mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-22 07:28:06 +00:00
Compare commits
99 Commits
v0.0.1
...
restart-on
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2fb8d7ef85 | ||
![]() |
ad02c6f6c0 | ||
![]() |
f439b04415 | ||
![]() |
ee12dd449a | ||
![]() |
e6ca8d49ba | ||
![]() |
f974d3f398 | ||
![]() |
d807e92c36 | ||
![]() |
244771717c | ||
![]() |
7aeb36e383 | ||
![]() |
5c2f840623 | ||
![]() |
670040477e | ||
![]() |
a45a310611 | ||
![]() |
d80e8dd799 | ||
![]() |
88728909ff | ||
![]() |
e365ebfdfa | ||
![]() |
2c4b4df01a | ||
![]() |
49d984c6f2 | ||
![]() |
72cad7284c | ||
![]() |
0193a98681 | ||
![]() |
f241d7423d | ||
![]() |
6043e0da0b | ||
![]() |
753cc5e9a3 | ||
![]() |
8cfe98073e | ||
![]() |
c0037526b0 | ||
![]() |
96b42e7c52 | ||
![]() |
579b5848da | ||
![]() |
dff934cbc3 | ||
![]() |
2096f4440f | ||
![]() |
b3fc707337 | ||
![]() |
b50d864b50 | ||
![]() |
1643385d9b | ||
![]() |
9441214733 | ||
![]() |
7e4e988813 | ||
![]() |
fb1262f1bd | ||
![]() |
68f084080e | ||
![]() |
a428fe7462 | ||
![]() |
ea2d1f8a09 | ||
![]() |
bd96d50a9b | ||
![]() |
859c9e3462 | ||
![]() |
9dabac4a55 | ||
![]() |
d927a08790 | ||
![]() |
933f7c4e2c | ||
![]() |
81eb9a521f | ||
![]() |
eb32bd7f94 | ||
![]() |
a5781af949 | ||
![]() |
0aa5781acd | ||
![]() |
700be4426f | ||
![]() |
76ef9aa372 | ||
![]() |
d7e6704314 | ||
![]() |
2443979602 | ||
![]() |
5b65196d31 | ||
![]() |
e7df8a485d | ||
![]() |
ded76138da | ||
![]() |
a5db6aeb81 | ||
![]() |
d45f682c37 | ||
![]() |
d0c1235e58 | ||
![]() |
9e8f621020 | ||
![]() |
8dd7a28456 | ||
![]() |
43b06dd7aa | ||
![]() |
e8e01d6578 | ||
![]() |
b53e017b77 | ||
![]() |
b2565cebf8 | ||
![]() |
9459d2e292 | ||
![]() |
0409b17ef4 | ||
![]() |
2e47b76d4c | ||
![]() |
1cca09df90 | ||
![]() |
b9cb92eb1b | ||
![]() |
b574e394ad | ||
![]() |
2b92214cf5 | ||
![]() |
5422e37d77 | ||
![]() |
ac05846062 | ||
![]() |
dcc37519ef | ||
![]() |
73b089df79 | ||
![]() |
97650573fd | ||
![]() |
42e348de91 | ||
![]() |
9946ce7ba6 | ||
![]() |
71ccfc6235 | ||
![]() |
6cb8b87560 | ||
![]() |
62ca0c25fd | ||
![]() |
990ac86297 | ||
![]() |
077142d9f2 | ||
![]() |
d8a969265c | ||
![]() |
d98f9172a0 | ||
![]() |
8635be0cab | ||
![]() |
0824aa0837 | ||
![]() |
10b7db3057 | ||
![]() |
e2fc9e228e | ||
![]() |
d0eafd93ab | ||
![]() |
6aea2ad31c | ||
![]() |
6c98b16766 | ||
![]() |
7e3ab368e2 | ||
![]() |
b60fa8a444 | ||
![]() |
854112caeb | ||
![]() |
eebb90e43b | ||
![]() |
9f708729a8 | ||
![]() |
143b6f9403 | ||
![]() |
76ee62519e | ||
![]() |
75b9f474ab | ||
![]() |
dae6e51112 |
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report bugs and errors found while using the Operator.
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Your environment
|
||||
|
||||
<!-- Version of the Operator when the error occurred -->
|
||||
Operator Version:
|
||||
|
||||
<!-- What version of the Connect server are you running?
|
||||
You can get this information from the Integrations section in 1Password
|
||||
https://start.1password.com/integrations/active
|
||||
-->
|
||||
Connect Server Version:
|
||||
|
||||
<!-- What version of Kubernetes have you deployed the operator to? -->
|
||||
Kubernetes Version:
|
||||
|
||||
## What happened?
|
||||
<!-- Describe the bug or error -->
|
||||
|
||||
## What did you expect to happen?
|
||||
<!-- Describe what should have happened -->
|
||||
|
||||
## Steps to reproduce
|
||||
1. <!-- Describe Steps to reproduce the issue -->
|
||||
|
||||
|
||||
## Notes & Logs
|
||||
<!-- Paste any logs here that may help with debugging.
|
||||
Remember to remove any sensitive information before sharing! -->
|
9
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
9
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# docs: https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 1Password Community
|
||||
url: https://1password.community/categories/secrets-automation
|
||||
about: Please ask general Secrets Automation questions here.
|
||||
- name: 1Password Security Bug Bounty
|
||||
url: https://bugcrowd.com/agilebits
|
||||
about: Please report security vulnerabilities here.
|
32
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for the Operator
|
||||
title: ''
|
||||
labels: feature-request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Summary
|
||||
<!-- Briefly describe the feature in one or two sentences. You can include more details later. -->
|
||||
|
||||
### Use cases
|
||||
<!-- Describe the use cases that make this feature useful to others.
|
||||
The description should help the reader understand why the feature is necessary.
|
||||
The better we understand your use case, the better we can help create an appropriate solution. -->
|
||||
|
||||
### Proposed solution
|
||||
<!-- If you already have an idea for how the feature should work, use this space to describe it.
|
||||
We'll work with you to find a workable approach, and any implementation details are appreciated.
|
||||
-->
|
||||
|
||||
### Is there a workaround to accomplish this today?
|
||||
<!-- If there's a way to accomplish this feature request without changes to the codebase, we'd like to hear it.
|
||||
-->
|
||||
|
||||
### References & Prior Work
|
||||
<!-- If a similar feature was implemented in another project or tool, add a link so we can better understand your request.
|
||||
Links to relevant documentation or RFCs are also appreciated. -->
|
||||
|
||||
* <!-- Reference 1 -->
|
||||
* <!-- Reference 2, etc -->
|
21
.github/workflows/build.yml
vendored
Normal file
21
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Build and Test
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.15
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./... -cover
|
85
.github/workflows/release-pr.yml
vendored
Normal file
85
.github/workflows/release-pr.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
on:
|
||||
create:
|
||||
branches:
|
||||
|
||||
name: Open Release PR for review
|
||||
|
||||
jobs:
|
||||
# This job is necessary because GitHub does not (yet) support
|
||||
# filtering `create` triggers by branch name.
|
||||
# See: https://github.community/t/trigger-job-on-branch-created/16878/5
|
||||
should_create_pr:
|
||||
name: Check if PR for branch already exists
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
result: ${{ steps.is_release_branch_without_pr.outputs.result }}
|
||||
steps:
|
||||
- id: is_release_branch_without_pr
|
||||
name: Find matching PR
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
// Search for an existing PR with head & base
|
||||
// that match the created branch
|
||||
|
||||
const [releaseBranchName] = context.ref.match("release/v[0-9]+\.[0-9]+\.[0-9]+") || []
|
||||
|
||||
if(!releaseBranchName) { return false }
|
||||
|
||||
const {data: prs} = await github.pulls.list({
|
||||
...context.repo,
|
||||
state: 'open',
|
||||
head: `1Password:${releaseBranchName}`,
|
||||
base: context.payload.master_branch
|
||||
})
|
||||
|
||||
return prs.length === 0
|
||||
|
||||
create_pr:
|
||||
needs: should_create_pr
|
||||
if: needs.should_create_pr.outputs.result == 'true'
|
||||
name: Create Release Pull Request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Parse release version
|
||||
id: get_version
|
||||
run: echo "::set-output name=version::$(echo $GITHUB_REF | sed 's|^refs/heads/release/v?*||g')"
|
||||
|
||||
- name: Prepare Pull Request
|
||||
id: prep_pr
|
||||
run: |
|
||||
CHANGELOG_PATH=$(printf "%s/CHANGELOG.md" "${GITHUB_WORKSPACE}")
|
||||
|
||||
LOG_ENTRY=$(awk '/START\/v[0-9]+\.[0-9]+\.[0-9]+*/{f=1; next} /---/{if (f == 1) exit} f' "${CHANGELOG_PATH}")
|
||||
export PR_BODY=$(cat <<EOF
|
||||
This is an automated PR for a new release.
|
||||
|
||||
Please check the following before approving:
|
||||
- [ ] Changelog is accurate. The documented changes for this release are printed below.
|
||||
- [ ] Any files referencing a version number. Confirm it matches the version number in the branch name.
|
||||
---
|
||||
## Release Changelog Preview
|
||||
${LOG_ENTRY}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Sanitizes multiline strings for action outputs (https://medium.com/agorapulse-stories/23f56447d209)
|
||||
PR_BODY="${PR_BODY//'%'/'%25'}"
|
||||
PR_BODY="${PR_BODY//$'\n'/'%0A'}"
|
||||
PR_BODY="${PR_BODY//$'\r'/'%0D'}"
|
||||
echo "::set-output name=pr_body::$(echo "$PR_BODY")"
|
||||
|
||||
- name: Create Pull Request via API
|
||||
id: post_pr
|
||||
uses: octokit/request-action@v2.x
|
||||
with:
|
||||
route: POST /repos/${{ github.repository }}/pulls
|
||||
title: ${{ format('Prepare Release - v{0}', steps.get_version.outputs.version) }}
|
||||
head: ${{ github.ref }}
|
||||
base: ${{ github.event.master_branch }}
|
||||
body: ${{ toJson(steps.prep_pr.outputs.pr_body) }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
57
.github/workflows/release.yml
vendored
Normal file
57
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release-docker:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_CLI_EXPERIMENTAL: "enabled"
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: crazy-max/ghaction-docker-meta@v2
|
||||
with:
|
||||
images: |
|
||||
1password/onepassword-operator
|
||||
# Publish image for x.y.z and x.y
|
||||
# The latest tag is automatically added for semver tags
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
- name: Get the version from tag
|
||||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Docker Login
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
operator_version=${{ steps.get_version.outputs.VERSION }}
|
79
CHANGELOG.md
Normal file
79
CHANGELOG.md
Normal file
@@ -0,0 +1,79 @@
|
||||
[//]: # (START/LATEST)
|
||||
# Latest
|
||||
|
||||
## Features
|
||||
* A user-friendly description of a new feature. {issue-number}
|
||||
|
||||
## Fixes
|
||||
* A user-friendly description of a fix. {issue-number}
|
||||
|
||||
## Security
|
||||
* A user-friendly description of a security fix. {issue-number}
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v1.1.0)
|
||||
# v1.1.0
|
||||
|
||||
## Fixes
|
||||
* Fix normalization for keys in a Secret's `data` section to allow upper- and lower-case alphanumeric characters. {#66}
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v1.0.2)
|
||||
# v1.0.2
|
||||
|
||||
## Fixes
|
||||
* Name normalizer added to handle non-conforming item names.
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v1.0.1)
|
||||
# v1.0.1
|
||||
|
||||
## Features
|
||||
* This release also contains an arm64 Docker image. {#20}
|
||||
* Docker images are also pushed to the :latest and :<major>.<minor> tags.
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v1.0.0)
|
||||
# v1.0.0
|
||||
|
||||
## Features:
|
||||
* Option to automatically deploy 1Password Connect via the operator
|
||||
* Ignore restart annotation when looking for 1Password annotations
|
||||
* Release Automation
|
||||
* Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource
|
||||
* Adding configuration for auto rolling restart on deployments
|
||||
* Configure Auto Restarts for a OnePasswordItem Custom Resource
|
||||
* Update Connect Dependencies to latest
|
||||
* Add Github action for building and testing operator
|
||||
## Fixes:
|
||||
* Fix spec field example for OnePasswordItem in readme
|
||||
* Casing of annotations are now consistent
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v0.0.2)
|
||||
# v0.0.2
|
||||
|
||||
## Features:
|
||||
* Items can now be accessed by either `vaults/<vault_id>/items/<item_id>` or `vaults/<vault_title>/items/<item_title>`
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v0.0.1)
|
||||
|
||||
# v0.0.1
|
||||
|
||||
Initial 1Password Operator release
|
||||
|
||||
## Features
|
||||
* watches for deployment creations with `onepassword` annotations and creates an associated kubernetes secret
|
||||
* watches for `onepasswordsecret` crd creations and creates an associated kubernetes secrets
|
||||
* watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes
|
||||
* restart pods when secret has been updated
|
||||
* cleanup of kubernetes secrets when deployment or `onepasswordsecret` is deleted
|
||||
|
||||
---
|
68
Makefile
Normal file
68
Makefile
Normal file
@@ -0,0 +1,68 @@
|
||||
export MAIN_BRANCH ?= main
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.PHONY: test build build/binary build/local clean test/coverage release/prepare release/tag .check_bump_type .check_git_clean help
|
||||
|
||||
GIT_BRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
WORKTREE_CLEAN := $(shell git status --porcelain 1>/dev/null 2>&1; echo $$?)
|
||||
SCRIPTS_DIR := $(CURDIR)/scripts
|
||||
|
||||
versionFile = $(CURDIR)/.VERSION
|
||||
curVersion := $(shell cat $(versionFile) | sed 's/^v//')
|
||||
|
||||
OPERATOR_NAME := onepassword-connect-operator
|
||||
DOCKER_IMG_TAG ?= $(OPERATOR_NAME):v$(curVersion)
|
||||
|
||||
test: ## Run test suite
|
||||
go test ./...
|
||||
|
||||
test/coverage: ## Run test suite with coverage report
|
||||
go test -v ./... -cover
|
||||
|
||||
build/operator: ## Build operator Docker image
|
||||
@docker build -f operator/Dockerfile --build-arg operator_version=$(curVersion) -t $(DOCKER_IMG_TAG) .
|
||||
@echo "Successfully built and tagged image."
|
||||
@echo "Tag: $(DOCKER_IMG_TAG)"
|
||||
|
||||
build/operator/local: ## Build local version of the operator Docker image
|
||||
@docker build -f operator/Dockerfile -t local/$(DOCKER_IMG_TAG) .
|
||||
|
||||
build/operator/binary: clean ## Build operator binary
|
||||
@mkdir -p dist
|
||||
@go build -mod vendor -a -o manager ./operator/cmd/manager/main.go
|
||||
@mv manager ./dist
|
||||
|
||||
clean:
|
||||
rm -rf ./dist
|
||||
|
||||
help: ## Prints this help message
|
||||
@grep -E '^[\/a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
|
||||
## Release functions =====================
|
||||
|
||||
release/prepare: .check_git_clean ## Updates changelog and creates release branch (call with 'release/prepare version=<new_version_number>')
|
||||
|
||||
@test $(version) || (echo "[ERROR] version argument not set."; exit 1)
|
||||
@git fetch --quiet origin $(MAIN_BRANCH)
|
||||
|
||||
@echo $(version) | tr -d '\n' | tee $(versionFile) &>/dev/null
|
||||
|
||||
@NEW_VERSION=$(version) $(SCRIPTS_DIR)/prepare-release.sh
|
||||
|
||||
release/tag: .check_git_clean ## Creates git tag
|
||||
@git pull --ff-only
|
||||
@echo "Applying tag 'v$(curVersion)' to HEAD..."
|
||||
@git tag --sign "v$(curVersion)" -m "Release v$(curVersion)"
|
||||
@echo "[OK] Success!"
|
||||
@echo "Remember to call 'git push --tags' to persist the tag."
|
||||
|
||||
## Helper functions =====================
|
||||
|
||||
.check_git_clean:
|
||||
ifneq ($(GIT_BRANCH), $(MAIN_BRANCH))
|
||||
@echo "[ERROR] Please checkout default branch '$(MAIN_BRANCH)' and re-run this command."; exit 1;
|
||||
endif
|
||||
ifneq ($(WORKTREE_CLEAN), 0)
|
||||
@echo "[ERROR] Uncommitted changes found in worktree. Address them and try again."; exit 1;
|
||||
endif
|
127
README.md
127
README.md
@@ -1,131 +1,22 @@
|
||||
# 1Password Connect Kubernetes Operator
|
||||
# 1Password for Kubernetes
|
||||
|
||||
This repository includes various tooling for integrating 1Password secrets wtih Kubernetes.
|
||||
|
||||
## 1Password Connect Kubernetes Operator
|
||||
|
||||
The 1Password Connect Kubernetes Operator provides the ability to integrate Kubernetes with 1Password. This Operator manages `OnePasswordItem` Custom Resource Definitions (CRDs) that define the location of an Item stored in 1Password. The `OnePasswordItem` CRD, when created, will be used to compose a Kubernetes Secret containing the contents of the specified item.
|
||||
|
||||
The 1Password Connect Kubernetes Operator also allows for Kubernetes Secrets to be composed from a 1Password Item through annotation of an Item Path on a deployment.
|
||||
|
||||
The 1Password Connect Kubernetes Operator will continually check for updates from 1Password for any Kubernetes Secret that it has generated. If a Kubernetes Secret is updated, any Deployment using that secret will be automatically restarted.
|
||||
The 1Password Connect Kubernetes Operator will continually check for updates from 1Password for any Kubernetes Secret that it has generated. If a Kubernetes Secret is updated, any Deployment using that secret can be automatically restarted.
|
||||
|
||||
## Setup
|
||||
[Click here for more details on the 1Password Kubernetes Operator](operator/README.md)
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- [1Password Command Line Tool Installed](https://1password.com/downloads/command-line/)
|
||||
- [kubectl installed](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
|
||||
- [docker installed](https://docs.docker.com/get-docker/)
|
||||
- [1Password Connect has been setup with an API token issued to be used with the operator.](https://support.b5dev.com/cs/connect)
|
||||
- [1Password Connect deployed to Kubernetes](https://support.b5dev.com/cs/connect)
|
||||
|
||||
### Kubernetes Operator Deployment
|
||||
|
||||
**Create Kubernetes Secret for OP_CONNECT_TOKEN**
|
||||
|
||||
```bash
|
||||
# where <OP_CONNECT_TOKEN> is the 1Password Connect API token
|
||||
$ kubectl create secret generic onepassword-token --from-literal=token=<OP_CONNECT_TOKEN>
|
||||
```
|
||||
|
||||
**Set Permissions For Operator**
|
||||
|
||||
We must create a service account, role, and role binding and Kubernetes. Examples can be found in the `/deploy` folder.
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f deploy/permissions.yaml
|
||||
```
|
||||
|
||||
**Create Custom One Password Secret Resource**
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f deploy/crds/onepassword.com_onepassworditems_crd.yaml
|
||||
```
|
||||
|
||||
**Deploying the Operator**
|
||||
|
||||
An example Deployment yaml can be found at `/deploy/operator.yaml`.
|
||||
|
||||
```yaml
|
||||
containers:
|
||||
- name: onepassword-operator
|
||||
image: 1password/onepassword-operator
|
||||
```
|
||||
|
||||
and update the image pull policy to `Always`
|
||||
|
||||
```yaml
|
||||
imagePullPolicy: Always
|
||||
```
|
||||
|
||||
To further configure the 1Password Kubernetes Operator the Following Environment variables can be set in the deployment yaml:
|
||||
|
||||
- **WATCH_NAMESPACE:** comma separated list of what Namespaces to watch for changes.
|
||||
- **OP_CONNECT_HOST** (required): Specifies the host name within Kubernetes in which to access the 1Password Connect.
|
||||
- **POLLING_INTERVAL** (default: 600)**:** The number of seconds ****the 1Password Kubernetes Operator will wait before checking for updates from 1Password Connect.
|
||||
|
||||
Apply the deployment file:
|
||||
|
||||
```yaml
|
||||
kubectl apply -f deploy/operator.yaml
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To create a Kubernetes Secret from a 1Password item, create a yaml file with the following
|
||||
|
||||
```yaml
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem # {insert_new_name}
|
||||
metadata:
|
||||
name: {item_name} #this name will also be used for naming the generated kubernetes secret
|
||||
spec:
|
||||
item-path: "vaults/{vaultId}/items/{itemId}"
|
||||
# where vaultId is the id of the vault in which to find the item
|
||||
# where itemId is the id of the item that you want to store as a Kubernetes Secret
|
||||
```
|
||||
|
||||
Deploy the OnePasswordItem to Kubernetes:
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f {your_item}.yaml
|
||||
```
|
||||
|
||||
To test that the Kubernetes Secret check that the following command returns a secret:
|
||||
|
||||
```bash
|
||||
$ kubectl get secret {secret_name}
|
||||
```
|
||||
|
||||
Note: Deleting the `OnePasswordItem` that you've created will automatically delete the created Kubernetes Secret.
|
||||
|
||||
To create a single Kubernetes Secret for a deployment, add the following annotations to the deployment metadata:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deployment-example
|
||||
annotations:
|
||||
onepasswordoperator/item-path: "vaults/{vaultId}/items/{itemId}"
|
||||
onepasswordoperator/item-name: "{secret_name}"
|
||||
```
|
||||
|
||||
Applying this yaml file will create a Kubernetes Secret with the name `{secret_name}` and contents from the location specified at the specified Item Path.
|
||||
|
||||
Note: Deleting the Deployment that you've created will automatically delete the created Kubernetes Secret only if the deployment is still annotated with `onepasswordoperator./item-path` and `onepasswordoperator/item-name` and no other deployment is using the secret.
|
||||
|
||||
If a 1Password Item that is linked to a Kubernetes Secret is updated within the `POLLING_INTERVAL` the associated Kubernetes Secret will be updated. Furthermore, any deployments using that secret will be given a rolling restart.
|
||||
|
||||
## Development
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
$ go test -v ./... -cover
|
||||
```
|
||||
|
||||
## Security
|
||||
# Security
|
||||
|
||||
1Password requests you practice responsible disclosure if you discover a vulnerability.
|
||||
|
||||
Please file requests via [**BugCrowd**](https://bugcrowd.com/agilebits).
|
||||
|
||||
For information about security practices, please visit our [Security homepage](https://bugcrowd.com/agilebits).
|
||||
For information about security practices, please visit our [Security homepage](https://bugcrowd.com/agilebits).
|
||||
|
@@ -1,15 +0,0 @@
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:latest
|
||||
|
||||
ENV OPERATOR=/usr/local/bin/onepassword-connect-operator \
|
||||
USER_UID=1001 \
|
||||
USER_NAME=onepassword-connect-operator
|
||||
|
||||
# install operator binary
|
||||
COPY build/_output/bin/op-kubernetes-connect-operator ${OPERATOR}
|
||||
|
||||
COPY build/bin /usr/local/bin
|
||||
RUN /usr/local/bin/user_setup
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint"]
|
||||
|
||||
USER ${USER_UID}
|
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
exec ${OPERATOR} $@
|
@@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -x
|
||||
|
||||
# ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be)
|
||||
echo "${USER_NAME}:x:${USER_UID}:0:${USER_NAME} user:${HOME}:/sbin/nologin" >> /etc/passwd
|
||||
mkdir -p "${HOME}"
|
||||
chown "${USER_UID}:0" "${HOME}"
|
||||
chmod ug+rwx "${HOME}"
|
||||
|
||||
# no need for this script to remain in the image after running
|
||||
rm "$0"
|
@@ -1,45 +0,0 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: onepassworditems.onepassword.com
|
||||
spec:
|
||||
group: onepassword.com
|
||||
names:
|
||||
kind: OnePasswordItem
|
||||
listKind: OnePasswordItemList
|
||||
plural: onepassworditems
|
||||
singular: onepassworditem
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: OnePasswordItem is the Schema for the onepassworditems API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: OnePasswordItemSpec defines the desired state of OnePasswordItem
|
||||
properties:
|
||||
item_path:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: OnePasswordItemStatus defines the observed state of OnePasswordItem
|
||||
type: object
|
||||
type: object
|
||||
version: v1
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
8
go.mod
8
go.mod
@@ -3,15 +3,13 @@ module github.com/1Password/onepassword-operator
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/1Password/connect-sdk-go v0.0.1
|
||||
github.com/go-logr/logr v0.1.0 // indirect
|
||||
github.com/1Password/connect-sdk-go v1.0.1
|
||||
github.com/operator-framework/operator-sdk v0.19.0
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/common v0.14.0 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.6.1
|
||||
go.etcd.io/etcd v3.3.25+incompatible // indirect
|
||||
github.com/suborbital/grav v0.4.1
|
||||
github.com/suborbital/vektor v0.5.0
|
||||
k8s.io/api v0.18.2
|
||||
k8s.io/apimachinery v0.18.2
|
||||
k8s.io/client-go v12.0.0+incompatible
|
||||
|
250
go.sum
250
go.sum
@@ -1,5 +1,4 @@
|
||||
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
||||
bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
|
||||
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.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
||||
@@ -19,8 +18,8 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
|
||||
cloud.google.com/go/storage v1.3.0/go.mod h1:9IAwXhoyBJ7z9LcAwkj0/7NnPzYaPeZxxVp3zm+5IqA=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/1Password/connect-sdk-go v0.0.1 h1:qsFZQDQ+JirZRwSom/p6zzNqkkcYAYx4EXivUyPhvBo=
|
||||
github.com/1Password/connect-sdk-go v0.0.1/go.mod h1:br2BWk2sqgJFnOFK5WSDfBBmwQ6E7hV9LoPqrtHGRNY=
|
||||
github.com/1Password/connect-sdk-go v1.0.1 h1:BOeMIxVk6/ISmLNWUkSxEbVI7tNr5+aNXIobMM0/I0U=
|
||||
github.com/1Password/connect-sdk-go v1.0.1/go.mod h1:br2BWk2sqgJFnOFK5WSDfBBmwQ6E7hV9LoPqrtHGRNY=
|
||||
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
|
||||
github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
@@ -44,6 +43,7 @@ github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSW
|
||||
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
|
||||
github.com/Azure/go-autorest/autorest/to v0.3.1-0.20191028180845-3492b2aff503/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.2.1-0.20191028180845-3492b2aff503/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI=
|
||||
@@ -51,24 +51,24 @@ github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1Gn
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
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/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU=
|
||||
github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
|
||||
github.com/Masterminds/squirrel v1.2.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA=
|
||||
github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
|
||||
github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk=
|
||||
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
@@ -129,15 +129,12 @@ github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/brancz/gojsontoyaml v0.0.0-20190425155809-e8bd32d46b3d/go.mod h1:IyUJYN1gvWjtLF5ZuygmxbnsAyP3aJS6cHzIuZY50B0=
|
||||
github.com/brancz/gojsontoyaml v0.0.0-20191212081931-bf2969bbd742/go.mod h1:IyUJYN1gvWjtLF5ZuygmxbnsAyP3aJS6cHzIuZY50B0=
|
||||
github.com/brancz/kube-rbac-proxy v0.5.0/go.mod h1:cL2VjiIFGS90Cjh5ZZ8+It6tMcBt8rwvuw2J6Mamnl0=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/bugsnag-go v1.5.3/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/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
@@ -154,7 +151,6 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
@@ -163,10 +159,11 @@ github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqh
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/containerd/containerd v1.2.7/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.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA=
|
||||
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
|
||||
github.com/containerd/continuity v0.0.0-20200228182428-0f16d7a0959c/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
|
||||
github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb h1:nXPkFq8X1a9ycY3GYQpFNxHh3j2JgY7zDZfq2EXMIzk=
|
||||
github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
|
||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
|
||||
@@ -175,27 +172,20 @@ github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8h
|
||||
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.17+incompatible h1:f/Z3EoDSx1yjaIjLQGo1diYUlQYSBrrAQ5vP8NjwXwo=
|
||||
github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/prometheus-operator v0.38.0 h1:gF2xYIfO09XLFdyEecND46uihQ2KTaDwTozRZpXLtN4=
|
||||
github.com/coreos/prometheus-operator v0.38.0/go.mod h1:xZC7/TgeC0/mBaJk+1H9dbHaiEvLYHgX6Mi1h40UPh8=
|
||||
github.com/coreos/prometheus-operator v0.38.1-0.20200424145508-7e176fda06cc h1:nMbUjGuF7UzVluucix/vsy4973BNdEiT/aX6kFtskKM=
|
||||
github.com/coreos/prometheus-operator v0.38.1-0.20200424145508-7e176fda06cc/go.mod h1:erio69w1R/aC14D5nfvAXSlE8FT8jt2Hnavc50Dp33A=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
@@ -233,20 +223,21 @@ github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyG
|
||||
github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||
github.com/docker/distribution v2.7.0+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 v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce h1:KXS1Jg+ddGcWA8e1N7cupxaHHZhit5rB9tfDU+mfjyY=
|
||||
github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
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.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/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=
|
||||
@@ -267,7 +258,6 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
@@ -282,16 +272,14 @@ github.com/fatih/structtag v1.1.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
|
||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/garyburd/redigo v1.6.0/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/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-bindata/go-bindata/v3 v3.1.3/go.mod h1:1/zrpXsLD8YDIbhZRqXzm1Ghc7NhEvIN9+Z6R5/xH4I=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
@@ -344,7 +332,6 @@ github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd
|
||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
@@ -370,18 +357,13 @@ github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6
|
||||
github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
|
||||
github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
|
||||
github.com/gobuffalo/flect v0.2.1/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc=
|
||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||
github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
|
||||
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -394,6 +376,7 @@ github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang-migrate/migrate/v4 v4.6.2/go.mod h1:JYi6reN3+Z734VZ0akNuyOJNcrg45ZL7LDBMW3WGJL0=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
|
||||
@@ -409,13 +392,11 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
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.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
@@ -433,8 +414,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.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.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
@@ -457,13 +437,14 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
|
||||
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
|
||||
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
|
||||
@@ -476,15 +457,15 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
|
||||
github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/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/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
@@ -501,7 +482,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.12.1 h1:zCy2xE9ablevUOrUZc3Dl72Dt+ya2FNAvC2yLYMHzi4=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
|
||||
github.com/grpc-ecosystem/grpc-health-probe v0.2.1-0.20181220223928-2bf0a5b182db/go.mod h1:uBKkC2RbarFsvS5jMJHpVhTLvGlGQj9JJwkaePE3FWI=
|
||||
github.com/grpc-ecosystem/grpc-health-probe v0.3.2/go.mod h1:izVOQ4RWbjUR6lm4nn+VLJyQ+FyaiGmprEYgI04Gs7U=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||
@@ -541,10 +521,8 @@ github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
||||
github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/serf v0.8.5/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc1oal+k=
|
||||
github.com/helm/helm-2to3 v0.5.1/go.mod h1:AXFpQX2cSQpss+47ROPEeu7Sm4+CRJ1jKWCEQdHP3/c=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
||||
@@ -583,27 +561,29 @@ github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGn
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jsonnet-bundler/jsonnet-bundler v0.2.0/go.mod h1:/by7P/OoohkI3q4CgSFqcoFsVY+IaNbzOVDknEsKDeU=
|
||||
github.com/jsonnet-bundler/jsonnet-bundler v0.3.1/go.mod h1:/by7P/OoohkI3q4CgSFqcoFsVY+IaNbzOVDknEsKDeU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
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/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.11.12/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 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
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 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE=
|
||||
github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
@@ -628,10 +608,8 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN
|
||||
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/maorfr/helm-plugin-utils v0.0.0-20200216074820-36d2fcf6ae86/go.mod h1:p3gwmRSFqbWw6plBpR0sKl3n3vpu8kX70gvCJKMvvCA=
|
||||
github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=
|
||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
||||
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-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
|
||||
@@ -645,7 +623,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
@@ -658,6 +635,7 @@ github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N
|
||||
github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RRf2RRxieJU=
|
||||
github.com/mikefarah/yq/v2 v2.4.1/go.mod h1:i8SYf1XdgUvY2OFwSqGAtWOOgimD2McJ6iutoxRm4k0=
|
||||
github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||
github.com/minio/minio-go/v6 v6.0.49/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
@@ -668,7 +646,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
||||
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
@@ -693,12 +670,19 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+
|
||||
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
|
||||
github.com/nats-io/jwt/v2 v2.0.2/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||
github.com/nats-io/nats-server/v2 v2.3.2/go.mod h1:dUf7Cm5z5LbciFVwWx54owyCKm8x4/hL6p7rrljhLFY=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
|
||||
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
@@ -715,6 +699,7 @@ github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
||||
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
@@ -724,22 +709,22 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
|
||||
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
|
||||
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 h1:yN8BPXVwMBAm3Cuvh1L5XE8XpvYRMdsVLd82ILprhUU=
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7/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/openshift/api v0.0.0-20200205133042-34f0ec8dab87/go.mod h1:fT6U/JfG8uZzemTRwZA2kBDJP5nWz7v05UHnty/D+pk=
|
||||
github.com/openshift/client-go v0.0.0-20190923180330-3b6373338c9b/go.mod h1:6rzn+JTr7+WYS2E1TExP4gByoABxMznR6y2SnUIkmxk=
|
||||
github.com/openshift/origin v0.0.0-20160503220234-8f127d736703/go.mod h1:0Rox5r9C8aQn6j1oAOQ0c1uC86mYbUFObzjBRvUKHII=
|
||||
github.com/openshift/prom-label-proxy v0.1.1-0.20191016113035-b8153a7f39f1/go.mod h1:p5MuxzsYP1JPsNGwtjtcgRHHlGziCJJfztff91nNixw=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
@@ -753,38 +738,14 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/operator-framework/api v0.1.1/go.mod h1:yzNYR7qyJqRGOOp+bT6Z/iYSbSPNxeh3Si93Gx/3OBY=
|
||||
github.com/operator-framework/api v0.3.4/go.mod h1:TmRmw+8XOUaDPq6SP9gA8cIexNf/Pq8LMFY7YaKQFTs=
|
||||
github.com/operator-framework/api v0.3.7-0.20200528122852-759ca0d84007/go.mod h1:Xbje9x0SHmh0nihE21kpesB38vk3cyxnE6JdDS8Jo1Q=
|
||||
github.com/operator-framework/api v0.3.7-0.20200602203552-431198de9fc2/go.mod h1:Xbje9x0SHmh0nihE21kpesB38vk3cyxnE6JdDS8Jo1Q=
|
||||
github.com/operator-framework/api v0.3.8/go.mod h1:Xbje9x0SHmh0nihE21kpesB38vk3cyxnE6JdDS8Jo1Q=
|
||||
github.com/operator-framework/api v0.3.13/go.mod h1:Xbje9x0SHmh0nihE21kpesB38vk3cyxnE6JdDS8Jo1Q=
|
||||
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20200321030439-57b580e57e88/go.mod h1:7Ut8p9jJ8C6RZyyhZfZypmlibCIJwK5Wcc+WZDgLkOA=
|
||||
github.com/operator-framework/operator-registry v1.5.3/go.mod h1:agrQlkWOo1q8U1SAaLSS2WQ+Z9vswNT2M2HFib9iuLY=
|
||||
github.com/operator-framework/operator-registry v1.6.1/go.mod h1:sx4wWMiZtYhlUiaKscg3QQUPPM/c1bkrAs4n4KipDb4=
|
||||
github.com/operator-framework/operator-registry v1.6.2-0.20200330184612-11867930adb5/go.mod h1:SHff373z8asEkPo6aWpN0qId4Y/feQTjZxRF8PRhti8=
|
||||
github.com/operator-framework/operator-registry v1.12.1/go.mod h1:rf4b/h77GUv1+geiej2KzGRQr8iBLF4dXNwr5AuGkrQ=
|
||||
github.com/operator-framework/operator-registry v1.12.4/go.mod h1:JChIivJVLE1wRbgIhDFzYQYT9yosa2wd6qiTyMuG5mg=
|
||||
github.com/operator-framework/operator-registry v1.12.6-0.20200611222234-275301b779f8/go.mod h1:loVINznYhgBIkmv83kU4yee88RS0BBk+hqOw9r4bhJk=
|
||||
github.com/operator-framework/operator-registry v1.13.4/go.mod h1:YhnIzOVjRU2ZwZtzt+fjcjW8ujJaSFynBEu7QVKaSdU=
|
||||
github.com/operator-framework/operator-sdk v0.17.0 h1:+TTrGjXa+lm7g7Cm0UtFcgOjnw1x9/lBorydpsIIhOY=
|
||||
github.com/operator-framework/operator-sdk v0.17.0/go.mod h1:wmYi08aoUmtgfoUamURmssI4dkdFGNtSI1Egj+ZfBnk=
|
||||
github.com/operator-framework/operator-sdk v0.18.0 h1:YdtgXvjHu+f0hE/Nzvw9JIU3XvOZyp2Kd2cOLW486rU=
|
||||
github.com/operator-framework/operator-sdk v0.18.0/go.mod h1:xP/DNvnYnIoGK1bLKiD0s7aYZp2fa4AI6t1v3INaoZg=
|
||||
github.com/operator-framework/operator-sdk v0.19.0 h1:pnHx1EjcP20miuBpxFC2s+uuPeItcrmRR6bOFhm4D6c=
|
||||
github.com/operator-framework/operator-sdk v0.19.0/go.mod h1:8MR6CguLizat2RGjdSMifGwW6mEMwKqAtZnSUHJ6SxU=
|
||||
github.com/operator-framework/operator-sdk v0.19.4 h1:QI6k+WBDAXagx2OunEajQLa8LZwmRXu+x/SwmVZ/CCw=
|
||||
github.com/operator-framework/operator-sdk v0.19.4/go.mod h1:+gIlE/CfBGFGj51qJ2sLTPZWE1X27cKtjZ0m5vwY8Hw=
|
||||
github.com/operator-framework/operator-sdk v1.1.0 h1:te2CWGrgu0zPlefs3qbVb0ipUQ1zQ4lG27piXbBWn58=
|
||||
github.com/operator-framework/operator-sdk v1.2.0 h1:Ql/UXtQQz21juAwsWV5lctcFwsNI90GKdf1/saJZWj0=
|
||||
github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc=
|
||||
github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=
|
||||
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776/go.mod h1:3HNVkVOU7vZeFXocWuvtcS0XSFLcf2XUSDHkq9t1jU4=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
|
||||
github.com/otiai10/mint v1.2.4/go.mod h1:d+b7n/0R3tdyUYYylALXpWQ/kTN+QobSq/4SRGBkR3M=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
@@ -877,7 +838,6 @@ github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
|
||||
github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rubenv/sql-migrate v0.0.0-20191025130928-9355dd04f4b3/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY=
|
||||
github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc=
|
||||
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=
|
||||
@@ -888,9 +848,14 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
|
||||
github.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/schollz/peerdiscovery v1.6.1 h1:2V/Dw+GZY18W6e3yAeUzJouHmIcr9UlWtqsQtpfObGw=
|
||||
github.com/schollz/peerdiscovery v1.6.1/go.mod h1:bq5/NB9o9/jyEwiW4ubehfToBa2LwdQQMoNiy/vSdYg=
|
||||
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sethvargo/go-envconfig v0.3.0/go.mod h1:XZ2JRR7vhlBEO5zMmOpLgUhgYltqYqq4d4tKagtPUv0=
|
||||
github.com/sethvargo/go-envconfig v0.3.2 h1:277Lb2iTpUZjUZu1qLoLa/aetwvtZbKh8wNWXmc6dSk=
|
||||
github.com/sethvargo/go-envconfig v0.3.2/go.mod h1:XZ2JRR7vhlBEO5zMmOpLgUhgYltqYqq4d4tKagtPUv0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
@@ -903,9 +868,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
@@ -952,6 +916,11 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/suborbital/grav v0.4.1 h1:g70gG4EVqNcy5LMII05ayLtxMD8v5M9kBW1BcJFYsC0=
|
||||
github.com/suborbital/grav v0.4.1/go.mod h1:jN+zB9O6ztW2GqruEU46EMOFjvc8K2UDLyofFJWdI8o=
|
||||
github.com/suborbital/vektor v0.2.2/go.mod h1:6YQE7r6t1JcVs3twpqjXDftsLUaTNUk5YorRKHcDamI=
|
||||
github.com/suborbital/vektor v0.5.0 h1:E5PPiBYboarFoprUmjjG/ieVCeIUpD/1F2MnVa/iaDs=
|
||||
github.com/suborbital/vektor v0.5.0/go.mod h1:iNMR6/alEK1D7fbupwIlGlK5LajngEvq/N+RGVWaqNw=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/thanos-io/thanos v0.11.0/go.mod h1:N/Yes7J68KqvmY+xM6J5CJqEvWIvKSR5sqGtmuD6wDc=
|
||||
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
@@ -985,14 +954,10 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
||||
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/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/gorelic v0.0.7/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/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
||||
go.elastic.co/apm v1.5.0/go.mod h1:OdB9sPtM6Vt7oz3VXt7+KR96i9li74qrxBGHTQygFvk=
|
||||
@@ -1004,9 +969,6 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.etcd.io/etcd v0.5.0-alpha.5 h1:VOolFSo3XgsmnYDLozjvZ6JL6AAwIDu1Yx1y+4EYLDo=
|
||||
go.etcd.io/etcd v3.3.25+incompatible h1:V1RzkZJj9LqsJRy+TUBgpWSbZXITLB819lstuTFoZOY=
|
||||
go.etcd.io/etcd v3.3.25+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
@@ -1030,6 +992,7 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
@@ -1053,17 +1016,22 @@ golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/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-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1080,12 +1048,14 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
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 h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
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.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1121,16 +1091,19 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
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 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343/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-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||
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 h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||
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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1154,6 +1127,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1183,14 +1157,11 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLg
|
||||
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-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191020212454-3e7259c5e7c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1203,6 +1174,14 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201113135734-0a15ea8d9b02/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180805044716-cb6730876b98/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1210,14 +1189,19 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -1240,7 +1224,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
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-20190624180213-70d37148ca0c/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-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
@@ -1261,13 +1244,13 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
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-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200115044656-831fdb1e1868/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200327195553-82bb89366a1e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200403190813-44a64ad78b9b h1:AFZdJUT7jJYXQEC29hYH/WZkoV7+KhwxQGmdZ19yYoY=
|
||||
golang.org/x/tools v0.0.0-20200403190813-44a64ad78b9b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
|
||||
@@ -1306,13 +1289,11 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
||||
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-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9 h1:6XzpBoANz1NqMNfDXzc2QmHmbb1vyMsvRfoP5rM+K1I=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24 h1:wDju+RU97qa0FZT0QnZDg9Uc2dH0Ql513kFvHocz+WM=
|
||||
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200701001935-0939c5918c31/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
@@ -1329,20 +1310,13 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200709232328-d8193ee9cc3e/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
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 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
@@ -1350,6 +1324,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
@@ -1379,7 +1354,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
@@ -1387,17 +1361,16 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
helm.sh/helm/v3 v3.1.0/go.mod h1:WYsFJuMASa/4XUqLyv54s0U/f3mlAaRErGmyy4z921g=
|
||||
helm.sh/helm/v3 v3.1.2/go.mod h1:WYsFJuMASa/4XUqLyv54s0U/f3mlAaRErGmyy4z921g=
|
||||
helm.sh/helm/v3 v3.2.0/go.mod h1:ZaXz/vzktgwjyGGFbUWtIQkscfE7WYoRGP2szqAFHR0=
|
||||
helm.sh/helm/v3 v3.2.4/go.mod h1:ZaXz/vzktgwjyGGFbUWtIQkscfE7WYoRGP2szqAFHR0=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
k8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A=
|
||||
@@ -1405,25 +1378,10 @@ k8s.io/api v0.0.0-20190813020757-36bff7324fb7/go.mod h1:3Iy+myeAORNCLgjd/Xu9ebwN
|
||||
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
|
||||
k8s.io/api v0.0.0-20191115095533-47f6de673b26/go.mod h1:iA/8arsvelvo4IDqIhX4IbjTEKBGgvsf2OraTuRtLFU=
|
||||
k8s.io/api v0.0.0-20191122220107-b5267f2975e0/go.mod h1:vYpRfxYkMrmPPSesoHEkGNHxNKTk96REAwqm/inQbs0=
|
||||
k8s.io/api v0.16.7/go.mod h1:oUAiGRgo4t+5yqcxjOu5LoHT3wJ8JSbgczkaFYS5L7I=
|
||||
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||
k8s.io/api v0.17.1/go.mod h1:zxiAc5y8Ngn4fmhWUtSxuUlkfz1ixT7j9wESokELzOg=
|
||||
k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
|
||||
k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0=
|
||||
k8s.io/api v0.17.4 h1:HbwOhDapkguO8lTAE8OX3hdF2qp8GtpC9CW/MQATXXo=
|
||||
k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA=
|
||||
k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
|
||||
k8s.io/api v0.18.2 h1:wG5g5ZmSVgm5B+eHMIbI9EGATS2L8Z72rda19RIEgY8=
|
||||
k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
|
||||
k8s.io/api v0.19.3 h1:GN6ntFnv44Vptj/b+OnMW7FmzkpDoIDLZRvKX3XH9aU=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
|
||||
k8s.io/apiextensions-apiserver v0.16.7/go.mod h1:6xYRp4trGp6eT5WZ6tPi/TB2nfWQCzwUvBlpg8iswe0=
|
||||
k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8=
|
||||
k8s.io/apiextensions-apiserver v0.17.2 h1:cP579D2hSZNuO/rZj9XFRzwJNYb41DbNANJb6Kolpss=
|
||||
k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
|
||||
k8s.io/apiextensions-apiserver v0.17.3/go.mod h1:CJbCyMfkKftAd/X/V6OTHYhVn7zXnDdnkUjS1h0GTeY=
|
||||
k8s.io/apiextensions-apiserver v0.17.4 h1:ZKFnw3cJrGZ/9s6y+DerTF4FL+dmK0a04A++7JkmMho=
|
||||
k8s.io/apiextensions-apiserver v0.17.4/go.mod h1:rCbbbaFS/s3Qau3/1HbPlHblrWpFivoaLYccCffvQGI=
|
||||
k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo=
|
||||
k8s.io/apiextensions-apiserver v0.18.2 h1:I4v3/jAuQC+89L3Z7dDgAiN4EOjN6sbm6iBqQwHTah8=
|
||||
k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
|
||||
@@ -1432,60 +1390,28 @@ k8s.io/apimachinery v0.0.0-20190809020650-423f5d784010/go.mod h1:Waf/xTS2FGRrgXC
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
|
||||
k8s.io/apimachinery v0.0.0-20191115015347-3c7067801da2/go.mod h1:dXFS2zaQR8fyzuvRdJDHw2Aerij/yVGJSre0bZQSVJA=
|
||||
k8s.io/apimachinery v0.0.0-20191121175448-79c2a76c473a/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apimachinery v0.16.7/go.mod h1:Xk2vD2TRRpuWYLQNM6lT9R7DSFZUYG03SarNkbGrnKE=
|
||||
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apimachinery v0.17.1/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
|
||||
k8s.io/apimachinery v0.17.4 h1:UzM+38cPUJnzqSQ+E1PY4YxMHIzQyCg29LOoGfo79Zw=
|
||||
k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
|
||||
k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
|
||||
k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA=
|
||||
k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
|
||||
k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc=
|
||||
k8s.io/apimachinery v0.19.3 h1:bpIQXlKjB4cB/oNpnNnV+BybGPR7iP5oYpsOTEJ4hgc=
|
||||
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
|
||||
k8s.io/apiserver v0.0.0-20191122221311-9d521947b1e1/go.mod h1:RbsZY5zzBIWnz4KbctZsTVjwIuOpTp4Z8oCgFHN4kZQ=
|
||||
k8s.io/apiserver v0.16.7/go.mod h1:/5zSatF30/L9zYfMTl55jzzOnx7r/gGv5a5wtRp8yAw=
|
||||
k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg=
|
||||
k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
|
||||
k8s.io/apiserver v0.17.3/go.mod h1:iJtsPpu1ZpEnHaNawpSV0nYTGBhhX2dUlnn7/QS7QiY=
|
||||
k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I=
|
||||
k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw=
|
||||
k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
|
||||
k8s.io/autoscaler v0.0.0-20190607113959-1b4f1855cb8e/go.mod h1:QEXezc9uKPT91dwqhSJq3GNI3B1HxFRQHiku9kmrsSA=
|
||||
k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI=
|
||||
k8s.io/cli-runtime v0.17.3/go.mod h1:X7idckYphH4SZflgNpOOViSxetiMj6xI0viMAjM81TA=
|
||||
k8s.io/cli-runtime v0.17.4/go.mod h1:IVW4zrKKx/8gBgNNkhiUIc7nZbVVNhc1+HcQh+PiNHc=
|
||||
k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ=
|
||||
k8s.io/cli-runtime v0.18.2/go.mod h1:yfFR2sQQzDsV0VEKGZtrJwEy4hLZ2oj4ZIfodgxAHWQ=
|
||||
k8s.io/client-go v0.17.4 h1:VVdVbpTY70jiNHS1eiFkUt7ZIJX3txd29nDxxXH4en8=
|
||||
k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc=
|
||||
k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE=
|
||||
k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
|
||||
k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
|
||||
k8s.io/code-generator v0.16.7/go.mod h1:wFdrXdVi/UC+xIfLi+4l9elsTT/uEF61IfcN2wOLULQ=
|
||||
k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
|
||||
k8s.io/code-generator v0.17.1/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
|
||||
k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
|
||||
k8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=
|
||||
k8s.io/code-generator v0.17.4/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=
|
||||
k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
|
||||
k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
|
||||
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
|
||||
k8s.io/component-base v0.0.0-20191122220729-2684fb322cb9/go.mod h1:NFuUusy/X4Tk21m21tcNUihnmp4OI7lXU7/xA+rYXkc=
|
||||
k8s.io/component-base v0.16.7/go.mod h1:ikdyfezOFMu5O0qJjy/Y9eXwj+fV3pVwdmt0ulVcIR0=
|
||||
k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=
|
||||
k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=
|
||||
k8s.io/component-base v0.17.3/go.mod h1:GeQf4BrgelWm64PXkIXiPh/XS0hnO42d9gx9BtbZRp8=
|
||||
k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE=
|
||||
k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c=
|
||||
k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20191010091904-7fa3014cb28f/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/helm v2.16.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
@@ -1493,7 +1419,6 @@ k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-aggregator v0.17.3/go.mod h1:1dMwMFQbmH76RKF0614L7dNenMl3dwnUJuOOyZ3GMXA=
|
||||
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
|
||||
k8s.io/kube-openapi v0.0.0-20190320154901-5e45bb682580/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
|
||||
k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4=
|
||||
@@ -1504,17 +1429,10 @@ k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDN
|
||||
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
|
||||
k8s.io/kube-state-metrics v1.7.2 h1:6vdtgXrrRRMSgnyDmgua+qvgCYv954JNfxXAtDkeLVQ=
|
||||
k8s.io/kube-state-metrics v1.7.2/go.mod h1:U2Y6DRi07sS85rmVPmBFlmv+2peBcL8IWGjM+IjYA/E=
|
||||
k8s.io/kubectl v0.17.2/go.mod h1:y4rfLV0n6aPmvbRCqZQjvOp3ezxsFgpqL+zF5jH/lxk=
|
||||
k8s.io/kubectl v0.17.3/go.mod h1:NUn4IBY7f7yCMwSop2HCXlw/MVYP4HJBiUmOR3n9w28=
|
||||
k8s.io/kubectl v0.17.4 h1:Ts0CvqvIVceS4RTVXgWMH+YqtieLAzyS2T9eoz8uDQ0=
|
||||
k8s.io/kubectl v0.17.4/go.mod h1:im5QWmh6fvtmJkkNm4HToLe8z9aM3jihYK5X/wOybcY=
|
||||
k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU=
|
||||
k8s.io/kubectl v0.18.2 h1:9jnGSOC2DDVZmMUTMi0D1aed438mfQcgqa5TAzVjA1k=
|
||||
k8s.io/kubectl v0.18.2/go.mod h1:OdgFa3AlsPKRpFFYE7ICTwulXOcMGXHTc+UKhHKvrb4=
|
||||
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||
k8s.io/metrics v0.17.2/go.mod h1:3TkNHET4ROd+NfzNxkjoVfQ0Ob4iZnaHmSEA4vYpwLw=
|
||||
k8s.io/metrics v0.17.3/go.mod h1:HEJGy1fhHOjHggW9rMDBJBD3YuGroH3Y1pnIRw9FFaI=
|
||||
k8s.io/metrics v0.17.4/go.mod h1:6rylW2iD3M9VppnEAAtJASY1XS8Pt9tcYh+tHxBeV3I=
|
||||
k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4=
|
||||
k8s.io/metrics v0.18.2/go.mod h1:qga8E7QfYNR9Q89cSCAjinC9pTZ7yv1XSVGUB0vJypg=
|
||||
k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
|
||||
@@ -1533,22 +1451,16 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
|
||||
sigs.k8s.io/controller-runtime v0.5.2 h1:pyXbUfoTo+HA3jeIfr0vgi+1WtmNh0CwlcnQGLXwsSw=
|
||||
sigs.k8s.io/controller-runtime v0.5.2/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A=
|
||||
sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvOk9NM=
|
||||
sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
|
||||
sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA=
|
||||
sigs.k8s.io/controller-tools v0.2.8/go.mod h1:9VKHPszmf2DHz/QmHkcfZoewO6BL7pPs9uAiBVsaJSE=
|
||||
sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI=
|
||||
sigs.k8s.io/kubebuilder v1.0.9-0.20200513134826-f07a0146a40b/go.mod h1:FGPx0hvP73+bapzWoy5ePuhAJYgJjrFbPxgvWyortM0=
|
||||
sigs.k8s.io/kubebuilder v1.0.9-0.20200618125005-36aa113dbe99/go.mod h1:FGPx0hvP73+bapzWoy5ePuhAJYgJjrFbPxgvWyortM0=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.2 h1:WiMoyniAVAYm03w+ImfF9IE2G23GLR/SwDnQyaNZvPk=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.2/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
|
@@ -7,12 +7,18 @@ COPY go.mod go.mod
|
||||
COPY go.sum go.sum
|
||||
|
||||
# Copy the go source
|
||||
COPY cmd/manager/main.go main.go
|
||||
COPY pkg/ pkg/
|
||||
COPY version/ version/
|
||||
COPY operator/cmd/manager/main.go operator/main.go
|
||||
COPY operator/pkg/ operator/pkg/
|
||||
COPY operator/version/ operator/version/
|
||||
COPY vendor/ vendor/
|
||||
# Build
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -mod vendor -a -o manager main.go
|
||||
ARG operator_version=dev
|
||||
RUN CGO_ENABLED=0 \
|
||||
GO111MODULE=on \
|
||||
go build \
|
||||
-ldflags "-X \"github.com/1Password/onepassword-operator/operator/version.Version=$operator_version\"" \
|
||||
-mod vendor \
|
||||
-a -o manager operator/main.go
|
||||
|
||||
# Use distroless as minimal base image to package the manager binary
|
||||
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||
@@ -20,5 +26,6 @@ FROM gcr.io/distroless/static:nonroot
|
||||
WORKDIR /
|
||||
COPY --from=builder /workspace/manager .
|
||||
USER nonroot:nonroot
|
||||
COPY operator/deploy/connect/ operator/deploy/connect/
|
||||
|
||||
ENTRYPOINT ["/manager"]
|
||||
ENTRYPOINT ["/manager"]
|
235
operator/README.md
Normal file
235
operator/README.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# 1Password Connect Kubernetes Operator
|
||||
|
||||
The 1Password Connect Kubernetes Operator provides the ability to integrate Kubernetes with 1Password. This Operator manages `OnePasswordItem` Custom Resource Definitions (CRDs) that define the location of an Item stored in 1Password. The `OnePasswordItem` CRD, when created, will be used to compose a Kubernetes Secret containing the contents of the specified item.
|
||||
|
||||
The 1Password Connect Kubernetes Operator also allows for Kubernetes Secrets to be composed from a 1Password Item through annotation of an Item Path on a deployment.
|
||||
|
||||
The 1Password Connect Kubernetes Operator will continually check for updates from 1Password for any Kubernetes Secret that it has generated. If a Kubernetes Secret is updated, any Deployment using that secret can be automatically restarted.
|
||||
|
||||
## Setup
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- [1Password Command Line Tool Installed](https://1password.com/downloads/command-line/)
|
||||
- [kubectl installed](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
|
||||
- [docker installed](https://docs.docker.com/get-docker/)
|
||||
- [Generated a 1password-credentials.json file and issued a 1Password Connect API Token for the K8s Operator integration](https://support.1password.com/secrets-automation/)
|
||||
- [1Password Connect deployed to Kubernetes](https://support.1password.com/connect-deploy-kubernetes/#step-2-deploy-a-1password-connect-server). **NOTE**: If customization of the 1Password Connect deployment is not required you can skip this prerequisite.
|
||||
|
||||
### Quickstart for Deploying 1Password Connect to Kubernetes
|
||||
|
||||
|
||||
#### Deploy with Helm
|
||||
The 1Password Connect Helm Chart helps to simplify the deployment of 1Password Connect and the 1Password Connect Kubernetes Operator to Kubernetes.
|
||||
|
||||
[The 1Password Connect Helm Chart can be found here.](https://github.com/1Password/connect-helm-charts)
|
||||
|
||||
#### Deploy using the Connect Operator
|
||||
If 1Password Connect is already running, you can skip this step. This guide will provide a quickstart option for deploying a default configuration of 1Password Connect via starting the deploying the 1Password Connect Operator, however it is recommended that you instead deploy your own manifest file if customization of the 1Password Connect deployment is desired.
|
||||
|
||||
Encode the 1password-credentials.json file you generated in the prerequisite steps and save it to a file named op-session:
|
||||
|
||||
```bash
|
||||
$ cat 1password-credentials.json | base64 | \
|
||||
tr '/+' '_-' | tr -d '=' | tr -d '\n' > op-session
|
||||
```
|
||||
|
||||
Create a Kubernetes secret from the op-session file:
|
||||
```bash
|
||||
|
||||
$ kubectl create secret generic op-credentials --from-file=1password-credentials.json
|
||||
```
|
||||
|
||||
Add the following environment variable to the onepassword-connect-operator container in `deploy/operator.yaml`:
|
||||
```yaml
|
||||
- name: MANAGE_CONNECT
|
||||
value: "true"
|
||||
```
|
||||
Adding this environment variable will have the operator automatically deploy a default configuration of 1Password Connect to the `default` namespace.
|
||||
### Kubernetes Operator Deployment
|
||||
|
||||
**Create Kubernetes Secret for OP_CONNECT_TOKEN**
|
||||
|
||||
"Create a Connect token for the operator and save it as a Kubernetes Secret:
|
||||
|
||||
```bash
|
||||
$ kubectl create secret generic onepassword-token --from-literal=token=<OP_CONNECT_TOKEN>"
|
||||
```
|
||||
|
||||
If you do not have a token for the operator, you can generate a token and save it to kubernetes with the following command:
|
||||
```bash
|
||||
$ kubectl create secret generic onepassword-token --from-literal=token=$(op create connect token <server> op-k8s-operator --vault <vault>)
|
||||
```
|
||||
|
||||
[More information on generating a token can be found here](https://support.1password.com/secrets-automation/#appendix-issue-additional-access-tokens)
|
||||
|
||||
**Set Permissions For Operator**
|
||||
|
||||
We must create a service account, role, and role binding and Kubernetes. Examples can be found in the `/deploy` folder.
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f deploy/permissions.yaml
|
||||
```
|
||||
|
||||
**Create Custom One Password Secret Resource**
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f deploy/crds/onepassword.com_onepassworditems_crd.yaml
|
||||
```
|
||||
|
||||
**Deploying the Operator**
|
||||
|
||||
An sample Deployment yaml can be found at `/deploy/operator.yaml`.
|
||||
|
||||
|
||||
To further configure the 1Password Kubernetes Operator the Following Environment variables can be set in the operator yaml:
|
||||
|
||||
- **OP_CONNECT_HOST** (required): Specifies the host name within Kubernetes in which to access the 1Password Connect.
|
||||
- **WATCH_NAMESPACE:** (default: watch all namespaces): Comma separated list of what Namespaces to watch for changes.
|
||||
- **POLLING_INTERVAL** (default: 600): The number of seconds the 1Password Kubernetes Operator will wait before checking for updates from 1Password Connect.
|
||||
- **MANAGE_CONNECT** (default: false): If set to true, on deployment of the operator, a default configuration of the OnePassword Connect Service will be deployed to the `default` namespace.
|
||||
- **AUTO_RESTART** (default: false): If set to true, the operator will restart any deployment using a secret from 1Password Connect. This can be overwritten by namespace, deployment, or individual secret. More details on AUTO_RESTART can be found in the ["Configuring Automatic Rolling Restarts of Deployments"](#configuring-automatic-rolling-restarts-of-deployments) section.
|
||||
|
||||
Apply the deployment file:
|
||||
|
||||
```yaml
|
||||
kubectl apply -f deploy/operator.yaml
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To create a Kubernetes Secret from a 1Password item, create a yaml file with the following
|
||||
|
||||
```yaml
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem
|
||||
metadata:
|
||||
name: <item_name> #this name will also be used for naming the generated kubernetes secret
|
||||
spec:
|
||||
itemPath: "vaults/<vault_id_or_title>/items/<item_id_or_title>"
|
||||
```
|
||||
|
||||
Deploy the OnePasswordItem to Kubernetes:
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f <your_item>.yaml
|
||||
```
|
||||
|
||||
To test that the Kubernetes Secret check that the following command returns a secret:
|
||||
|
||||
```bash
|
||||
$ kubectl get secret <secret_name>
|
||||
```
|
||||
|
||||
Note: Deleting the `OnePasswordItem` that you've created will automatically delete the created Kubernetes Secret.
|
||||
|
||||
To create a single Kubernetes Secret for a deployment, add the following annotations to the deployment metadata:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deployment-example
|
||||
annotations:
|
||||
operator.1password.io/item-path: "vaults/<vault_id_or_title>/items/<item_id_or_title>"
|
||||
operator.1password.io/item-name: "<secret_name>"
|
||||
```
|
||||
|
||||
Applying this yaml file will create a Kubernetes Secret with the name `<secret_name>` and contents from the location specified at the specified Item Path.
|
||||
|
||||
Note: Deleting the Deployment that you've created will automatically delete the created Kubernetes Secret only if the deployment is still annotated with `operator.1password.io/item-path` and `operator.1password.io/item-name` and no other deployment is using the secret.
|
||||
|
||||
If a 1Password Item that is linked to a Kubernetes Secret is updated within the POLLING_INTERVAL the associated Kubernetes Secret will be updated. However, if you do not want a specific secret to be updated you can add the tag `operator.1password.io:ignore-secret` to the item stored in 1Password. While this tag is in place, any updates made to an item will not trigger an update to the associated secret in Kubernetes.
|
||||
|
||||
---
|
||||
**NOTE**
|
||||
|
||||
If multiple 1Password vaults/items have the same `title` when using a title in the access path, the desired action will be performed on the oldest vault/item.
|
||||
|
||||
Titles and field names that include white space and other characters that are not a valid [DNS subdomain name](https://kubernetes.io/docs/concepts/configuration/secret/) will create Kubernetes secrets that have titles and fields in the following format:
|
||||
- Invalid characters before the first alphanumeric character and after the last alphanumeric character will be removed
|
||||
- All whitespaces between words will be replaced by `-`
|
||||
- All the letters will be lower-cased.
|
||||
|
||||
---
|
||||
|
||||
### Configuring Automatic Rolling Restarts of Deployments
|
||||
|
||||
If a 1Password Item that is linked to a Kubernetes Secret is updated, any deployments configured to `auto-restart` AND are using that secret will be given a rolling restart the next time 1Password Connect is polled for updates.
|
||||
|
||||
There are many levels of granularity on which to configure auto restarts on deployments: at the operator level, per-namespace, or per-deployment.
|
||||
|
||||
**On the operator**: This method allows for managing auto restarts on all deployments within the namespaces watched by operator. Auto restarts can be enabled by setting the environemnt variable `AUTO_RESTART` to true. If the value is not set, the operator will default this value to false.
|
||||
|
||||
**Per Namespace**: This method allows for managing auto restarts on all deployments within a namespace. Auto restarts can by managed by setting the annotation `operator.1password.io/auto-restart` to either `true` or `false` on the desired namespace. An example of this is shown below:
|
||||
```yaml
|
||||
# enabled auto restarts for all deployments within a namespace unless overwritten within a deployment
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "example-namespace"
|
||||
annotations:
|
||||
operator.1password.io/auto-restart: "true"
|
||||
```
|
||||
If the value is not set, the auto reset settings on the operator will be used. This value can be overwritten by deployment.
|
||||
|
||||
**Per Deployment**
|
||||
This method allows for managing auto restarts on a given deployment. Auto restarts can by managed by setting the annotation `operator.1password.io/auto-restart` to either `true` or `false` on the desired deployment. An example of this is shown below:
|
||||
```yaml
|
||||
# enabled auto restarts for the deployment
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: "example-deployment"
|
||||
annotations:
|
||||
operator.1password.io/auto-restart: "true"
|
||||
```
|
||||
If the value is not set, the auto reset settings on the namespace will be used.
|
||||
|
||||
**Per OnePasswordItem Custom Resource**
|
||||
This method allows for managing auto restarts on a given OnePasswordItem custom resource. Auto restarts can by managed by setting the annotation `operator.1password.io/auto_restart` to either `true` or `false` on the desired OnePasswordItem. An example of this is shown below:
|
||||
```yaml
|
||||
# enabled auto restarts for the OnePasswordItem
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem
|
||||
metadata:
|
||||
name: example
|
||||
annotations:
|
||||
operator.1password.io/auto-restart: "true"
|
||||
```
|
||||
If the value is not set, the auto reset settings on the deployment will be used.
|
||||
|
||||
## Development
|
||||
|
||||
### Creating a Docker image
|
||||
|
||||
To create a local version of the Docker image for testing, use the following `Makefile` target:
|
||||
```shell
|
||||
make build/local
|
||||
```
|
||||
|
||||
### Building the Operator binary
|
||||
```shell
|
||||
make build/binary
|
||||
```
|
||||
|
||||
The binary will be placed inside a `dist` folder within this repository.
|
||||
|
||||
### Running Tests
|
||||
|
||||
```shell
|
||||
make test
|
||||
```
|
||||
|
||||
With coverage:
|
||||
```shell
|
||||
make test/coverage
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
1Password requests you practice responsible disclosure if you discover a vulnerability.
|
||||
|
||||
Please file requests via [**BugCrowd**](https://bugcrowd.com/agilebits).
|
||||
|
||||
For information about security practices, please visit our [Security homepage](https://bugcrowd.com/agilebits).
|
@@ -11,16 +11,20 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/controller"
|
||||
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/controller"
|
||||
op "github.com/1Password/onepassword-operator/operator/pkg/onepassword"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/onepassword/message"
|
||||
"github.com/suborbital/grav/discovery/local"
|
||||
"github.com/suborbital/grav/grav"
|
||||
"github.com/suborbital/grav/transport/websocket"
|
||||
|
||||
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
|
||||
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/apis"
|
||||
"github.com/1Password/onepassword-operator/version"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/apis"
|
||||
"github.com/1Password/onepassword-operator/operator/version"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
|
||||
@@ -40,7 +44,11 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
|
||||
)
|
||||
|
||||
const connectBusPortVariable = "OP_BUS_PORT"
|
||||
const envHostVariable = "OP_CONNECT_HOST"
|
||||
const envPollingIntervalVariable = "POLLING_INTERVAL"
|
||||
const manageConnect = "MANAGE_CONNECT"
|
||||
const restartDeploymentsEnvVariable = "AUTO_RESTART"
|
||||
const defaultPollingInterval = 600
|
||||
|
||||
// Change below variables to serve metrics on different host or port.
|
||||
@@ -81,9 +89,11 @@ func main() {
|
||||
|
||||
printVersion()
|
||||
|
||||
namespace, err := k8sutil.GetWatchNamespace()
|
||||
namespace := os.Getenv(k8sutil.WatchNamespaceEnvVar)
|
||||
|
||||
deploymentNamespace, err := k8sutil.GetOperatorNamespace()
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to get watch namespace")
|
||||
log.Error(err, "Failed to get namespace")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -131,6 +141,27 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
//Setup 1PasswordConnect
|
||||
if shouldManageConnect() {
|
||||
log.Info("Automated Connect Management Enabled")
|
||||
go func() {
|
||||
connectStarted := false
|
||||
for connectStarted == false {
|
||||
err := op.SetupConnect(mgr.GetClient(), deploymentNamespace)
|
||||
// Cache Not Started is an acceptable error. Retry until cache is started.
|
||||
if err != nil && !errors.Is(err, &cache.ErrCacheNotStarted{}) {
|
||||
log.Error(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err == nil {
|
||||
connectStarted = true
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
log.Info("Automated Connect Management Disabled")
|
||||
}
|
||||
|
||||
// Setup One Password Client
|
||||
opConnectClient, err := connect.NewClientFromEnvironment()
|
||||
|
||||
@@ -142,21 +173,28 @@ func main() {
|
||||
// Add the Metrics Service
|
||||
addMetrics(ctx, cfg)
|
||||
|
||||
// Setup update secrets task
|
||||
updatedSecretsPoller := op.NewManager(mgr.GetClient(), opConnectClient)
|
||||
_, connectSet := os.LookupEnv(envHostVariable)
|
||||
done := make(chan bool)
|
||||
ticker := time.NewTicker(getPollingIntervalForUpdatingSecrets())
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
ticker.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
updatedSecretsPoller.UpdateKubernetesSecretsTask()
|
||||
updateSecretsHandler := op.NewManager(mgr.GetClient(), opConnectClient, shouldAutoRestartDeployments())
|
||||
// Setup update secrets task
|
||||
if connectSet {
|
||||
consumeConnectEvents(*updateSecretsHandler)
|
||||
} else {
|
||||
// If not using connect then we will use polling to get secret updates
|
||||
// TODO implement 1Password events-api
|
||||
ticker := time.NewTicker(getPollingIntervalForUpdatingSecrets())
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
ticker.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
updateSecretsHandler.UpdateKubernetesSecretsTask("", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}()
|
||||
}
|
||||
|
||||
// Start the Cmd
|
||||
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
|
||||
@@ -249,3 +287,66 @@ func getPollingIntervalForUpdatingSecrets() time.Duration {
|
||||
log.Info(fmt.Sprintf("Using default polling interval of %v seconds", defaultPollingInterval))
|
||||
return time.Duration(defaultPollingInterval) * time.Second
|
||||
}
|
||||
|
||||
func shouldManageConnect() bool {
|
||||
shouldManageConnect, found := os.LookupEnv(manageConnect)
|
||||
if found {
|
||||
shouldManageConnectBool, err := strconv.ParseBool(strings.ToLower(shouldManageConnect))
|
||||
if err != nil {
|
||||
log.Error(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
return shouldManageConnectBool
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func shouldAutoRestartDeployments() bool {
|
||||
shouldAutoRestartDeployments, found := os.LookupEnv(restartDeploymentsEnvVariable)
|
||||
if found {
|
||||
shouldAutoRestartDeploymentsBool, err := strconv.ParseBool(strings.ToLower(shouldAutoRestartDeployments))
|
||||
if err != nil {
|
||||
log.Error(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
return shouldAutoRestartDeploymentsBool
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func consumeConnectEvents(updateSecretsHandler op.SecretUpdateHandler) {
|
||||
gwss := websocket.New()
|
||||
locald := local.New()
|
||||
|
||||
port, found := os.LookupEnv(connectBusPortVariable)
|
||||
if !found {
|
||||
port = "42829"
|
||||
}
|
||||
|
||||
g := grav.New(
|
||||
grav.UseEndpoint(port, fmt.Sprintf("%s/meta/message", envHostVariable)),
|
||||
grav.UseTransport(gwss),
|
||||
grav.UseDiscovery(locald),
|
||||
)
|
||||
|
||||
pod := g.Connect()
|
||||
pod.OnType(message.TypeItemUpdate, ItemUpdate(updateSecretsHandler))
|
||||
}
|
||||
|
||||
// ItemUpdateEvent Grav message handler for activity.event messages. Starts update
|
||||
// process for updating Kubernetes Secrets and OnePasswordItems and triggers
|
||||
// auto restarts for deployments
|
||||
func ItemUpdate(updateSecretsHandler op.SecretUpdateHandler) grav.MsgFunc {
|
||||
return func(msg grav.Message) error {
|
||||
e := message.ItemUpdateEvent{}
|
||||
if err := msg.UnmarshalData(&e); err != nil {
|
||||
log.Error(err, "failed to UnmarshalData into Event")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("Detected update for item %s at vault %s", e.ItemUUID, e.VaultUUID))
|
||||
updateSecretsHandler.UpdateKubernetesSecretsTask("", "")
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
68
operator/deploy/connect/deployment.yaml
Normal file
68
operator/deploy/connect/deployment.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: onepassword-connect
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: onepassword-connect
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: onepassword-connect
|
||||
version: "1.0.0"
|
||||
spec:
|
||||
volumes:
|
||||
- name: shared-data
|
||||
emptyDir: {}
|
||||
- name: credentials
|
||||
secret:
|
||||
secretName: op-credentials
|
||||
initContainers:
|
||||
- name: sqlite-permissions
|
||||
image: alpine:3.12
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-c"
|
||||
args:
|
||||
- "mkdir -p /home/opuser/.op/data && chown -R 999 /home/opuser && chmod -R 700 /home/opuser && chmod -f -R 600 /home/opuser/.op/config || :"
|
||||
volumeMounts:
|
||||
- mountPath: /home/opuser/.op/data
|
||||
name: shared-data
|
||||
containers:
|
||||
- name: connect-api
|
||||
image: 1password/connect-api:latest
|
||||
resources:
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
cpu: "0.2"
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: OP_SESSION
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: op-credentials
|
||||
key: op-session
|
||||
volumeMounts:
|
||||
- mountPath: /home/opuser/.op/data
|
||||
name: shared-data
|
||||
- name: connect-sync
|
||||
image: 1password/connect-sync:latest
|
||||
resources:
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
cpu: "0.2"
|
||||
ports:
|
||||
- containerPort: 8081
|
||||
env:
|
||||
- name: OP_HTTP_PORT
|
||||
value: "8081"
|
||||
- name: OP_SESSION
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: op-credentials
|
||||
key: op-session
|
||||
volumeMounts:
|
||||
- mountPath: /home/opuser/.op/data
|
||||
name: shared-data
|
15
operator/deploy/connect/service.yaml
Normal file
15
operator/deploy/connect/service.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: onepassword-connect
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: onepassword-connect
|
||||
ports:
|
||||
- port: 8080
|
||||
name: connect-api
|
||||
nodePort: 31080
|
||||
- port: 8081
|
||||
name: connect-sync
|
||||
nodePort: 31081
|
@@ -0,0 +1,42 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: onepassworditems.onepassword.com
|
||||
spec:
|
||||
group: onepassword.com
|
||||
names:
|
||||
kind: OnePasswordItem
|
||||
listKind: OnePasswordItemList
|
||||
plural: onepassworditems
|
||||
singular: onepassworditem
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: OnePasswordItem is the Schema for the onepassworditems API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: OnePasswordItemSpec defines the desired state of OnePasswordItem
|
||||
properties:
|
||||
itemPath:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: OnePasswordItemStatus defines the observed state of OnePasswordItem
|
||||
type: object
|
||||
type: object
|
@@ -17,7 +17,6 @@ spec:
|
||||
- name: onepassword-connect-operator
|
||||
image: 1password/onepassword-operator
|
||||
command: ["/manager"]
|
||||
imagePullPolicy: Never
|
||||
env:
|
||||
- name: WATCH_NAMESPACE
|
||||
value: "default"
|
||||
@@ -28,7 +27,7 @@ spec:
|
||||
- name: OPERATOR_NAME
|
||||
value: "onepassword-connect-operator"
|
||||
- name: OP_CONNECT_HOST
|
||||
value: "http://secret-service:8080"
|
||||
value: "http://onepassword-connect:8080"
|
||||
- name: POLLING_INTERVAL
|
||||
value: "10"
|
||||
- name: OP_CONNECT_TOKEN
|
||||
@@ -36,3 +35,5 @@ spec:
|
||||
secretKeyRef:
|
||||
name: onepassword-token
|
||||
key: token
|
||||
- name: AUTO_RESTART
|
||||
value: "false"
|
@@ -16,9 +16,7 @@ spec:
|
||||
containers:
|
||||
- name: onepassword-connect-operator
|
||||
image: 1password/onepassword-operator
|
||||
command:
|
||||
- onepassword-connect-operator
|
||||
imagePullPolicy: Never
|
||||
command: ["/manager"]
|
||||
env:
|
||||
- name: WATCH_NAMESPACE
|
||||
value: "default,development"
|
||||
@@ -29,7 +27,7 @@ spec:
|
||||
- name: OPERATOR_NAME
|
||||
value: "onepassword-connect-operator"
|
||||
- name: OP_CONNECT_HOST
|
||||
value: "http://secret-service:8080"
|
||||
value: "http://onepassword-connect:8080"
|
||||
- name: POLLING_INTERVAL
|
||||
value: "10"
|
||||
- name: OP_CONNECT_TOKEN
|
||||
@@ -37,3 +35,5 @@ spec:
|
||||
secretKeyRef:
|
||||
name: onepassword-token
|
||||
key: token
|
||||
- name: AUTO_RESTART
|
||||
value: "false"
|
@@ -3,7 +3,7 @@ kind: ServiceAccount
|
||||
metadata:
|
||||
name: onepassword-connect-operator
|
||||
---
|
||||
kind: RoleBinding
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: onepassword-connect-operator-default
|
||||
@@ -13,12 +13,12 @@ subjects:
|
||||
name: onepassword-connect-operator
|
||||
namespace: default
|
||||
roleRef:
|
||||
kind: Role
|
||||
kind: ClusterRole
|
||||
name: onepassword-connect-operator
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: onepassword-connect-operator
|
||||
@@ -34,6 +34,7 @@ rules:
|
||||
- events
|
||||
- configmaps
|
||||
- secrets
|
||||
- namespaces
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
@@ -3,7 +3,7 @@ kind: ServiceAccount
|
||||
metadata:
|
||||
name: onepassword-connect-operator
|
||||
---
|
||||
kind: RoleBinding
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: onepassword-connect-operator-default
|
||||
@@ -17,7 +17,7 @@ roleRef:
|
||||
name: onepassword-connect-operator
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
kind: RoleBinding
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: onepassword-connect-operator-development
|
||||
@@ -48,6 +48,7 @@ rules:
|
||||
- events
|
||||
- configmaps
|
||||
- secrets
|
||||
- namespaces
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
@@ -1,7 +1,7 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
v1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1"
|
||||
v1 "github.com/1Password/onepassword-operator/operator/pkg/apis/onepassword/v1"
|
||||
)
|
||||
|
||||
func init() {
|
@@ -1,7 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/1Password/onepassword-operator/pkg/controller/deployment"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/controller/deployment"
|
||||
)
|
||||
|
||||
func init() {
|
@@ -1,7 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/1Password/onepassword-operator/pkg/controller/onepassworditem"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/controller/onepassworditem"
|
||||
)
|
||||
|
||||
func init() {
|
@@ -3,10 +3,12 @@ package deployment
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
|
||||
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||
"github.com/1Password/onepassword-operator/pkg/utils"
|
||||
kubeSecrets "github.com/1Password/onepassword-operator/operator/pkg/kubernetessecrets"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/onepassword"
|
||||
op "github.com/1Password/onepassword-operator/operator/pkg/onepassword"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/utils"
|
||||
|
||||
"regexp"
|
||||
|
||||
@@ -28,7 +30,7 @@ import (
|
||||
var log = logf.Log.WithName("controller_deployment")
|
||||
var finalizer = "onepassword.com/finalizer.secret"
|
||||
|
||||
const annotationRegExpString = "^onepasswordoperator\\/[a-zA-Z\\.]+"
|
||||
const annotationRegExpString = "^operator.1password.io\\/[a-zA-Z\\.]+"
|
||||
|
||||
func Add(mgr manager.Manager, opConnectClient connect.Client) error {
|
||||
return add(mgr, newReconciler(mgr, opConnectClient))
|
||||
@@ -99,7 +101,7 @@ func (r *ReconcileDeployment) Reconcile(request reconcile.Request) (reconcile.Re
|
||||
|
||||
annotations, annotationsFound := op.GetAnnotationsForDeployment(deployment, r.opAnnotationRegExp)
|
||||
if !annotationsFound {
|
||||
reqLogger.Info("No One Password Annotations found")
|
||||
reqLogger.Info("No 1Password Annotations found")
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
@@ -114,7 +116,7 @@ func (r *ReconcileDeployment) Reconcile(request reconcile.Request) (reconcile.Re
|
||||
}
|
||||
}
|
||||
// Handles creation or updating secrets for deployment if needed
|
||||
if err := r.HandleApplyingDeployment(deployment.Namespace, annotations, request); err != nil {
|
||||
if err := r.HandleApplyingDeployment(deployment, annotations, request); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
return reconcile.Result{}, nil
|
||||
@@ -142,7 +144,7 @@ func (r *ReconcileDeployment) cleanupKubernetesSecretForDeployment(secretName st
|
||||
if len(secretName) == 0 {
|
||||
return nil
|
||||
}
|
||||
updatedSecrets := map[string]bool{secretName: true}
|
||||
updatedSecrets := map[string]*corev1.Secret{secretName: kubernetesSecret}
|
||||
|
||||
multipleDeploymentsUsingSecret, err := r.areMultipleDeploymentsUsingSecret(updatedSecrets, *deletedDeployment)
|
||||
if err != nil {
|
||||
@@ -160,7 +162,7 @@ func (r *ReconcileDeployment) cleanupKubernetesSecretForDeployment(secretName st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileDeployment) areMultipleDeploymentsUsingSecret(updatedSecrets map[string]bool, deletedDeployment appsv1.Deployment) (bool, error) {
|
||||
func (r *ReconcileDeployment) areMultipleDeploymentsUsingSecret(updatedSecrets map[string]*corev1.Secret, deletedDeployment appsv1.Deployment) (bool, error) {
|
||||
deployments := &appsv1.DeploymentList{}
|
||||
opts := []client.ListOption{
|
||||
client.InNamespace(deletedDeployment.Namespace),
|
||||
@@ -187,10 +189,19 @@ func (r *ReconcileDeployment) removeOnePasswordFinalizerFromDeployment(deploymen
|
||||
return r.kubeClient.Update(context.Background(), deployment)
|
||||
}
|
||||
|
||||
func (r *ReconcileDeployment) HandleApplyingDeployment(namespace string, annotations map[string]string, request reconcile.Request) error {
|
||||
func (r *ReconcileDeployment) HandleApplyingDeployment(deployment *appsv1.Deployment, annotations map[string]string, request reconcile.Request) error {
|
||||
reqLog := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
|
||||
namespace := deployment.Namespace
|
||||
|
||||
// check if deployment is marked to be injected with secrets via the webhook
|
||||
injectedContainers, injected := annotations[op.ContainerInjectAnnotation]
|
||||
if injected {
|
||||
parsedInjectedContainers := strings.Split(injectedContainers, ",")
|
||||
return onepassword.CreateOnePasswordItemResourceFromDeployment(r.opConnectClient, r.kubeClient, deployment, parsedInjectedContainers)
|
||||
}
|
||||
|
||||
secretName := annotations[op.NameAnnotation]
|
||||
secretLabels := map[string]string(nil)
|
||||
if len(secretName) == 0 {
|
||||
reqLog.Info("No 'item-name' annotation set. 'item-path' and 'item-name' must be set as annotations to add new secret.")
|
||||
return nil
|
||||
@@ -201,5 +212,5 @@ func (r *ReconcileDeployment) HandleApplyingDeployment(namespace string, annotat
|
||||
return fmt.Errorf("Failed to retrieve item: %v", err)
|
||||
}
|
||||
|
||||
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item)
|
||||
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, annotations)
|
||||
}
|
@@ -6,8 +6,8 @@ import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/mocks"
|
||||
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/mocks"
|
||||
op "github.com/1Password/onepassword-operator/operator/pkg/onepassword"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -258,7 +258,7 @@ var tests = []testReconcileItem{
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Test Do not update if OnePassword Item Version has not changed",
|
||||
testName: "Test Do not update if Annotations have not changed",
|
||||
deploymentResource: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
@@ -271,6 +271,7 @@ var tests = []testReconcileItem{
|
||||
op.ItemPathAnnotation: itemPath,
|
||||
op.NameAnnotation: name,
|
||||
},
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
@@ -279,6 +280,8 @@ var tests = []testReconcileItem{
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
op.ItemPathAnnotation: itemPath,
|
||||
op.NameAnnotation: name,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
@@ -290,7 +293,10 @@ var tests = []testReconcileItem{
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
op.ItemPathAnnotation: itemPath,
|
||||
op.NameAnnotation: name,
|
||||
},
|
||||
Labels: map[string]string(nil),
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1"
|
||||
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||
"github.com/1Password/onepassword-operator/pkg/utils"
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/operator/pkg/apis/onepassword/v1"
|
||||
kubeSecrets "github.com/1Password/onepassword-operator/operator/pkg/kubernetessecrets"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/onepassword"
|
||||
op "github.com/1Password/onepassword-operator/operator/pkg/onepassword"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/utils"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
|
||||
@@ -143,11 +144,21 @@ func (r *ReconcileOnePasswordItem) removeOnePasswordFinalizerFromOnePasswordItem
|
||||
|
||||
func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error {
|
||||
secretName := resource.GetName()
|
||||
labels := resource.Labels
|
||||
annotations := resource.Annotations
|
||||
autoRestart := annotations[op.RestartDeploymentsAnnotation]
|
||||
|
||||
// do not create kubernetes secret if the OnePasswordItem was generated
|
||||
// due to secret being injected container via webhook
|
||||
_, injectedSecret := annotations[op.InjectedAnnotation]
|
||||
if injectedSecret {
|
||||
return nil
|
||||
}
|
||||
|
||||
item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to retrieve item: %v", err)
|
||||
}
|
||||
|
||||
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item)
|
||||
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, annotations)
|
||||
}
|
@@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/mocks"
|
||||
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/mocks"
|
||||
op "github.com/1Password/onepassword-operator/operator/pkg/onepassword"
|
||||
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1"
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/operator/pkg/apis/onepassword/v1"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -31,6 +31,9 @@ const (
|
||||
itemId = "nwrhuano7bcwddcviubpp4mhfq"
|
||||
username = "test-user"
|
||||
password = "QmHumKc$mUeEem7caHtbaBaJ"
|
||||
firstHost = "http://localhost:8080"
|
||||
awsKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
||||
iceCream = "freezing blue 20%"
|
||||
userKey = "username"
|
||||
passKey = "password"
|
||||
version = 123
|
||||
@@ -116,7 +119,8 @@ var tests = []testReconcileItem{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
op.ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
@@ -127,7 +131,8 @@ var tests = []testReconcileItem{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
op.ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
@@ -147,6 +152,11 @@ var tests = []testReconcileItem{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
op.ItemPathAnnotation: itemPath,
|
||||
},
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Spec: onepasswordv1.OnePasswordItemSpec{
|
||||
ItemPath: itemPath,
|
||||
@@ -157,8 +167,10 @@ var tests = []testReconcileItem{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
op.VersionAnnotation: "456",
|
||||
op.VersionAnnotation: "456",
|
||||
op.ItemPathAnnotation: itemPath,
|
||||
},
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
@@ -168,8 +180,10 @@ var tests = []testReconcileItem{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
op.ItemPathAnnotation: itemPath,
|
||||
},
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
@@ -210,6 +224,120 @@ var tests = []testReconcileItem{
|
||||
passKey: password,
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Secret from 1Password item with invalid K8s labels",
|
||||
customResource: &onepasswordv1.OnePasswordItem{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: onePasswordItemKind,
|
||||
APIVersion: onePasswordItemAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "!my sECReT it3m%",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: onepasswordv1.OnePasswordItemSpec{
|
||||
ItemPath: itemPath,
|
||||
},
|
||||
},
|
||||
existingSecret: nil,
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-secret-it3m",
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Secret from 1Password item with fields and sections that have invalid K8s labels",
|
||||
customResource: &onepasswordv1.OnePasswordItem{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: onePasswordItemKind,
|
||||
APIVersion: onePasswordItemAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "!my sECReT it3m%",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: onepasswordv1.OnePasswordItemSpec{
|
||||
ItemPath: itemPath,
|
||||
},
|
||||
},
|
||||
existingSecret: nil,
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-secret-it3m",
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"password": []byte(password),
|
||||
"username": []byte(username),
|
||||
"first-host": []byte(firstHost),
|
||||
"AWS-Access-Key": []byte(awsKey),
|
||||
"ice-cream-type": []byte(iceCream),
|
||||
},
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
"first host": firstHost,
|
||||
"AWS Access Key": awsKey,
|
||||
"😄 ice-cream type": iceCream,
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Secret from 1Password item with `-`, `_` and `.`",
|
||||
customResource: &onepasswordv1.OnePasswordItem{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: onePasswordItemKind,
|
||||
APIVersion: onePasswordItemAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "!.my_sECReT.it3m%-_",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: onepasswordv1.OnePasswordItemSpec{
|
||||
ItemPath: itemPath,
|
||||
},
|
||||
},
|
||||
existingSecret: nil,
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-secret.it3m",
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
op.VersionAnnotation: fmt.Sprint(version),
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"password": []byte(password),
|
||||
"username": []byte(username),
|
||||
"first-host": []byte(firstHost),
|
||||
"AWS-Access-Key": []byte(awsKey),
|
||||
"-_ice_cream.type.": []byte(iceCream),
|
||||
},
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
"first host": firstHost,
|
||||
"AWS Access Key": awsKey,
|
||||
"😄 -_ice_cream.type.": iceCream,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestReconcileOnePasswordItem(t *testing.T) {
|
||||
@@ -241,7 +369,10 @@ func TestReconcileOnePasswordItem(t *testing.T) {
|
||||
mocks.GetGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
|
||||
item := onepassword.Item{}
|
||||
item.Fields = generateFields(testData.opItem["username"], testData.opItem["password"])
|
||||
item.Fields = []*onepassword.ItemField{}
|
||||
for k, v := range testData.opItem {
|
||||
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||
}
|
||||
item.Version = version
|
||||
item.Vault.ID = vaultUUID
|
||||
item.ID = uuid
|
||||
@@ -257,8 +388,8 @@ func TestReconcileOnePasswordItem(t *testing.T) {
|
||||
// watched resource .
|
||||
req := reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Name: testData.customResource.ObjectMeta.Name,
|
||||
Namespace: testData.customResource.ObjectMeta.Namespace,
|
||||
},
|
||||
}
|
||||
_, err := r.Reconcile(req)
|
121
operator/pkg/kubernetessecrets/kubernetes_secrets_builder.go
Normal file
121
operator/pkg/kubernetessecrets/kubernetes_secrets_builder.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package kubernetessecrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"regexp"
|
||||
|
||||
"reflect"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/utils"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kubeValidate "k8s.io/apimachinery/pkg/util/validation"
|
||||
|
||||
kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
const OnepasswordPrefix = "operator.1password.io"
|
||||
const NameAnnotation = OnepasswordPrefix + "/item-name"
|
||||
const VersionAnnotation = OnepasswordPrefix + "/item-version"
|
||||
const restartAnnotation = OnepasswordPrefix + "/last-restarted"
|
||||
const ItemPathAnnotation = OnepasswordPrefix + "/item-path"
|
||||
const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart"
|
||||
|
||||
var log = logf.Log
|
||||
|
||||
func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]string, secretAnnotations map[string]string) error {
|
||||
|
||||
itemVersion := fmt.Sprint(item.Version)
|
||||
|
||||
// If secretAnnotations is nil we create an empty map so we can later assign values for the OP Annotations in the map
|
||||
if secretAnnotations == nil {
|
||||
secretAnnotations = map[string]string{}
|
||||
}
|
||||
|
||||
secretAnnotations[VersionAnnotation] = itemVersion
|
||||
secretAnnotations[ItemPathAnnotation] = fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID)
|
||||
|
||||
if autoRestart != "" {
|
||||
_, err := utils.StringToBool(autoRestart)
|
||||
if err != nil {
|
||||
log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName)
|
||||
return err
|
||||
}
|
||||
secretAnnotations[RestartDeploymentsAnnotation] = autoRestart
|
||||
}
|
||||
secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, *item)
|
||||
|
||||
currentSecret := &corev1.Secret{}
|
||||
err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
log.Info(fmt.Sprintf("Creating Secret %v at namespace '%v'", secret.Name, secret.Namespace))
|
||||
return kubeClient.Create(context.Background(), secret)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(currentSecret.Annotations, secretAnnotations) || !reflect.DeepEqual(currentSecret.Labels, labels) {
|
||||
log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace))
|
||||
currentSecret.ObjectMeta.Annotations = secretAnnotations
|
||||
currentSecret.ObjectMeta.Labels = labels
|
||||
currentSecret.Data = secret.Data
|
||||
return kubeClient.Update(context.Background(), currentSecret)
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("Secret with name %v and version %v already exists", secret.Name, secret.Annotations[VersionAnnotation]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, item onepassword.Item) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: utils.FormatSecretName(name),
|
||||
Namespace: namespace,
|
||||
Annotations: annotations,
|
||||
Labels: labels,
|
||||
},
|
||||
Data: BuildKubernetesSecretData(item.Fields),
|
||||
}
|
||||
}
|
||||
|
||||
func BuildKubernetesSecretData(fields []*onepassword.ItemField) map[string][]byte {
|
||||
secretData := map[string][]byte{}
|
||||
for i := 0; i < len(fields); i++ {
|
||||
if fields[i].Value != "" {
|
||||
key := formatSecretDataName(fields[i].Label)
|
||||
secretData[key] = []byte(fields[i].Value)
|
||||
}
|
||||
}
|
||||
return secretData
|
||||
}
|
||||
|
||||
// formatSecretDataName rewrites a value to be a valid Secret data key.
|
||||
//
|
||||
// The Secret data keys must consist of alphanumeric numbers, `-`, `_` or `.`
|
||||
// (https://kubernetes.io/docs/concepts/configuration/secret/#overview-of-secrets)
|
||||
func formatSecretDataName(value string) string {
|
||||
if errs := kubeValidate.IsConfigMapKey(value); len(errs) == 0 {
|
||||
return value
|
||||
}
|
||||
return createValidSecretDataName(value)
|
||||
}
|
||||
|
||||
var invalidDataChars = regexp.MustCompile("[^a-zA-Z0-9-._]+")
|
||||
var invalidStartEndChars = regexp.MustCompile("(^[^a-zA-Z0-9-._]+|[^a-zA-Z0-9-._]+$)")
|
||||
|
||||
func createValidSecretDataName(value string) string {
|
||||
result := invalidStartEndChars.ReplaceAllString(value, "")
|
||||
result = invalidDataChars.ReplaceAllString(result, "-")
|
||||
|
||||
if len(result) > kubeValidate.DNS1123SubdomainMaxLength {
|
||||
result = result[0:kubeValidate.DNS1123SubdomainMaxLength]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
@@ -9,10 +9,13 @@ import (
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kubeValidate "k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
const restartDeploymentAnnotation = "false"
|
||||
|
||||
type k8s struct {
|
||||
clientset kubernetes.Interface
|
||||
}
|
||||
@@ -28,7 +31,11 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
item.ID = "h46bb3jddvay7nxopfhvlwg35q"
|
||||
|
||||
kubeClient := fake.NewFakeClient()
|
||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item)
|
||||
secretLabels := map[string]string{}
|
||||
secretAnnotations := map[string]string{
|
||||
"testAnnotation": "exists",
|
||||
}
|
||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretAnnotations)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -40,6 +47,10 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
}
|
||||
compareFields(item.Fields, createdSecret.Data, t)
|
||||
compareAnnotationsToItem(createdSecret.Annotations, item, t)
|
||||
|
||||
if createdSecret.Annotations["testAnnotation"] != "exists" {
|
||||
t.Errorf("Expected testAnnotation to be merged with existing annotations, but wasn't.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
@@ -53,7 +64,9 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
item.ID = "h46bb3jddvay7nxopfhvlwg35q"
|
||||
|
||||
kubeClient := fake.NewFakeClient()
|
||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item)
|
||||
secretLabels := map[string]string{}
|
||||
secretAnnotations := map[string]string{}
|
||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretAnnotations)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -64,7 +77,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
newItem.Version = 456
|
||||
newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
newItem.ID = "h46bb3jddvay7nxopfhvlwg35q"
|
||||
err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem)
|
||||
err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretAnnotations)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -97,9 +110,10 @@ func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
}
|
||||
item := onepassword.Item{}
|
||||
item.Fields = generateFields(5)
|
||||
labels := map[string]string{}
|
||||
|
||||
kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, item)
|
||||
if kubeSecret.Name != name {
|
||||
kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, item)
|
||||
if kubeSecret.Name != strings.ToLower(name) {
|
||||
t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name)
|
||||
}
|
||||
if kubeSecret.Namespace != namespace {
|
||||
@@ -111,6 +125,45 @@ func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
compareFields(item.Fields, kubeSecret.Data, t)
|
||||
}
|
||||
|
||||
func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) {
|
||||
name := "inV@l1d k8s secret%name"
|
||||
expectedName := "inv-l1d-k8s-secret-name"
|
||||
namespace := "someNamespace"
|
||||
annotations := map[string]string{
|
||||
"annotationKey": "annotationValue",
|
||||
}
|
||||
labels := map[string]string{}
|
||||
item := onepassword.Item{}
|
||||
|
||||
item.Fields = []*onepassword.ItemField{
|
||||
{
|
||||
Label: "label w%th invalid ch!rs-",
|
||||
Value: "value1",
|
||||
},
|
||||
{
|
||||
Label: strings.Repeat("x", kubeValidate.DNS1123SubdomainMaxLength+1),
|
||||
Value: "name exceeds max length",
|
||||
},
|
||||
}
|
||||
|
||||
kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, item)
|
||||
|
||||
// Assert Secret's meta.name was fixed
|
||||
if kubeSecret.Name != expectedName {
|
||||
t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name)
|
||||
}
|
||||
if kubeSecret.Namespace != namespace {
|
||||
t.Errorf("Expected namespace value: %v but got: %v", namespace, kubeSecret.Namespace)
|
||||
}
|
||||
|
||||
// assert labels were fixed for each data key
|
||||
for key := range kubeSecret.Data {
|
||||
if !validLabel(key) {
|
||||
t.Errorf("Expected valid kubernetes label, got %s", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func compareAnnotationsToItem(annotations map[string]string, item onepassword.Item, t *testing.T) {
|
||||
actualVaultId, actualItemId, err := ParseVaultIdAndItemIdFromPath(annotations[ItemPathAnnotation])
|
||||
if err != nil {
|
||||
@@ -125,6 +178,10 @@ func compareAnnotationsToItem(annotations map[string]string, item onepassword.It
|
||||
if annotations[VersionAnnotation] != fmt.Sprint(item.Version) {
|
||||
t.Errorf("Expected annotation version to be %v but was %v", item.Version, annotations[VersionAnnotation])
|
||||
}
|
||||
|
||||
if annotations[RestartDeploymentsAnnotation] != "false" {
|
||||
t.Errorf("Expected restart deployments annotation to be %v but was %v", restartDeploymentAnnotation, RestartDeploymentsAnnotation)
|
||||
}
|
||||
}
|
||||
|
||||
func compareFields(actualFields []*onepassword.ItemField, secretData map[string][]byte, t *testing.T) {
|
||||
@@ -158,3 +215,10 @@ func ParseVaultIdAndItemIdFromPath(path string) (string, string, error) {
|
||||
}
|
||||
return "", "", fmt.Errorf("%q is not an acceptable path for One Password item. Must be of the format: `vaults/{vault_id}/items/{item_id}`", path)
|
||||
}
|
||||
|
||||
func validLabel(v string) bool {
|
||||
if err := kubeValidate.IsConfigMapKey(v); len(err) > 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
66
operator/pkg/mocks/mocksecretserver.go
Normal file
66
operator/pkg/mocks/mocksecretserver.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
)
|
||||
|
||||
type TestClient struct {
|
||||
GetVaultsFunc func() ([]onepassword.Vault, error)
|
||||
GetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error)
|
||||
GetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error)
|
||||
GetItemsFunc func(vaultUUID string) ([]onepassword.Item, error)
|
||||
GetItemsByTitleFunc func(title string, vaultUUID string) ([]onepassword.Item, error)
|
||||
GetItemByTitleFunc func(title string, vaultUUID string) (*onepassword.Item, error)
|
||||
CreateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
UpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
DeleteItemFunc func(item *onepassword.Item, vaultUUID string) error
|
||||
}
|
||||
|
||||
var (
|
||||
GetGetVaultsFunc func() ([]onepassword.Vault, error)
|
||||
DoGetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error)
|
||||
GetGetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error)
|
||||
DoGetItemsByTitleFunc func(title string, vaultUUID string) ([]onepassword.Item, error)
|
||||
DoGetItemByTitleFunc func(title string, vaultUUID string) (*onepassword.Item, error)
|
||||
DoCreateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
DoDeleteItemFunc func(item *onepassword.Item, vaultUUID string) error
|
||||
DoGetItemsFunc func(vaultUUID string) ([]onepassword.Item, error)
|
||||
DoUpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
)
|
||||
|
||||
// Do is the mock client's `Do` func
|
||||
func (m *TestClient) GetVaults() ([]onepassword.Vault, error) {
|
||||
return GetGetVaultsFunc()
|
||||
}
|
||||
|
||||
func (m *TestClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) {
|
||||
return DoGetVaultsByTitleFunc(title)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
return GetGetItemFunc(uuid, vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItems(vaultUUID string) ([]onepassword.Item, error) {
|
||||
return DoGetItemsFunc(vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItemsByTitle(title, vaultUUID string) ([]onepassword.Item, error) {
|
||||
return DoGetItemsByTitleFunc(title, vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItemByTitle(title string, vaultUUID string) (*onepassword.Item, error) {
|
||||
return DoGetItemByTitleFunc(title, vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) CreateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) {
|
||||
return DoCreateItemFunc(item, vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) DeleteItem(item *onepassword.Item, vaultUUID string) error {
|
||||
return DoDeleteItemFunc(item, vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) {
|
||||
return DoUpdateItemFunc(item, vaultUUID)
|
||||
}
|
62
operator/pkg/onepassword/annotations.go
Normal file
62
operator/pkg/onepassword/annotations.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
OnepasswordPrefix = "operator.1password.io"
|
||||
ItemPathAnnotation = OnepasswordPrefix + "/item-path"
|
||||
NameAnnotation = OnepasswordPrefix + "/item-name"
|
||||
VersionAnnotation = OnepasswordPrefix + "/item-version"
|
||||
RestartAnnotation = OnepasswordPrefix + "/last-restarted"
|
||||
RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart"
|
||||
ContainerInjectAnnotation = OnepasswordPrefix + "/inject"
|
||||
InjectedAnnotation = OnepasswordPrefix + "/injected"
|
||||
)
|
||||
|
||||
func GetAnnotationsForDeployment(deployment *appsv1.Deployment, regex *regexp.Regexp) (map[string]string, bool) {
|
||||
annotationsFound := false
|
||||
annotations := FilterAnnotations(deployment.Annotations, regex)
|
||||
if len(annotations) > 0 {
|
||||
annotationsFound = true
|
||||
} else {
|
||||
annotations = FilterAnnotations(deployment.Spec.Template.Annotations, regex)
|
||||
if len(annotations) > 0 {
|
||||
annotationsFound = true
|
||||
} else {
|
||||
annotationsFound = false
|
||||
}
|
||||
}
|
||||
|
||||
return annotations, annotationsFound
|
||||
}
|
||||
|
||||
func FilterAnnotations(annotations map[string]string, regex *regexp.Regexp) map[string]string {
|
||||
filteredAnnotations := make(map[string]string)
|
||||
for key, value := range annotations {
|
||||
if regex.MatchString(key) && key != RestartAnnotation && key != RestartDeploymentsAnnotation {
|
||||
filteredAnnotations[key] = value
|
||||
}
|
||||
}
|
||||
return filteredAnnotations
|
||||
}
|
||||
|
||||
func AreAnnotationsUsingSecrets(annotations map[string]string, secrets map[string]*corev1.Secret) bool {
|
||||
_, ok := secrets[annotations[NameAnnotation]]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AppendAnnotationUpdatedSecret(annotations map[string]string, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret {
|
||||
secret, ok := secrets[annotations[NameAnnotation]]
|
||||
if ok {
|
||||
updatedDeploymentSecrets[secret.Name] = secret
|
||||
}
|
||||
return updatedDeploymentSecrets
|
||||
}
|
@@ -7,7 +7,7 @@ import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
)
|
||||
|
||||
const AnnotationRegExpString = "^onepasswordoperator\\/[a-zA-Z\\.]+"
|
||||
const AnnotationRegExpString = "^operator.1password.io\\/[a-zA-Z\\.]+"
|
||||
|
||||
func TestFilterAnnotations(t *testing.T) {
|
||||
invalidAnnotation1 := "onepasswordconnect/vaultId"
|
117
operator/pkg/onepassword/connect_setup.go
Normal file
117
operator/pkg/onepassword/connect_setup.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"context"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"os"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
errors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
var logConnectSetup = logf.Log.WithName("ConnectSetup")
|
||||
var deploymentPath = "deploy/connect/deployment.yaml"
|
||||
var servicePath = "deploy/connect/service.yaml"
|
||||
|
||||
func SetupConnect(kubeClient client.Client, deploymentNamespace string) error {
|
||||
err := setupService(kubeClient, servicePath, deploymentNamespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = setupDeployment(kubeClient, deploymentPath, deploymentNamespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupDeployment(kubeClient client.Client, deploymentPath string, deploymentNamespace string) error {
|
||||
existingDeployment := &appsv1.Deployment{}
|
||||
|
||||
// check if deployment has already been created
|
||||
err := kubeClient.Get(context.Background(), types.NamespacedName{Name: "onepassword-connect", Namespace: deploymentNamespace}, existingDeployment)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
logConnectSetup.Info("No existing Connect deployment found. Creating Deployment")
|
||||
return createDeployment(kubeClient, deploymentPath, deploymentNamespace)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func createDeployment(kubeClient client.Client, deploymentPath string, deploymentNamespace string) error {
|
||||
deployment, err := getDeploymentToCreate(deploymentPath, deploymentNamespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = kubeClient.Create(context.Background(), deployment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDeploymentToCreate(deploymentPath string, deploymentNamespace string) (*appsv1.Deployment, error) {
|
||||
f, err := os.Open(deploymentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deployment := &appsv1.Deployment{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: deploymentNamespace,
|
||||
},
|
||||
}
|
||||
|
||||
err = yaml.NewYAMLOrJSONDecoder(f, 4096).Decode(deployment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deployment, nil
|
||||
}
|
||||
|
||||
func setupService(kubeClient client.Client, servicePath string, deploymentNamespace string) error {
|
||||
existingService := &corev1.Service{}
|
||||
|
||||
//check if service has already been created
|
||||
err := kubeClient.Get(context.Background(), types.NamespacedName{Name: "onepassword-connect", Namespace: deploymentNamespace}, existingService)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
logConnectSetup.Info("No existing Connect service found. Creating Service")
|
||||
return createService(kubeClient, servicePath, deploymentNamespace)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func createService(kubeClient client.Client, servicePath string, deploymentNamespace string) error {
|
||||
f, err := os.Open(servicePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := &corev1.Service{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: deploymentNamespace,
|
||||
},
|
||||
}
|
||||
|
||||
err = yaml.NewYAMLOrJSONDecoder(f, 4096).Decode(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = kubeClient.Create(context.Background(), service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
65
operator/pkg/onepassword/connect_setup_test.go
Normal file
65
operator/pkg/onepassword/connect_setup_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
var defaultNamespacedName = types.NamespacedName{Name: "onepassword-connect", Namespace: "default"}
|
||||
|
||||
func TestServiceSetup(t *testing.T) {
|
||||
|
||||
// Register operator types with the runtime scheme.
|
||||
s := scheme.Scheme
|
||||
|
||||
// Objects to track in the fake client.
|
||||
objs := []runtime.Object{}
|
||||
|
||||
// Create a fake client to mock API calls.
|
||||
client := fake.NewFakeClientWithScheme(s, objs...)
|
||||
|
||||
err := setupService(client, "../../deploy/connect/service.yaml", defaultNamespacedName.Namespace)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error Setting Up Connect: %v", err)
|
||||
}
|
||||
|
||||
// check that service was created
|
||||
service := &corev1.Service{}
|
||||
err = client.Get(context.TODO(), defaultNamespacedName, service)
|
||||
if err != nil {
|
||||
t.Errorf("Error Setting Up Connect service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentSetup(t *testing.T) {
|
||||
|
||||
// Register operator types with the runtime scheme.
|
||||
s := scheme.Scheme
|
||||
|
||||
// Objects to track in the fake client.
|
||||
objs := []runtime.Object{}
|
||||
|
||||
// Create a fake client to mock API calls.
|
||||
client := fake.NewFakeClientWithScheme(s, objs...)
|
||||
|
||||
err := setupDeployment(client, "../../deploy/connect/deployment.yaml", defaultNamespacedName.Namespace)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error Setting Up Connect: %v", err)
|
||||
}
|
||||
|
||||
// check that deployment was created
|
||||
deployment := &appsv1.Deployment{}
|
||||
err = client.Get(context.TODO(), defaultNamespacedName, deployment)
|
||||
if err != nil {
|
||||
t.Errorf("Error Setting Up Connect deployment: %v", err)
|
||||
}
|
||||
}
|
63
operator/pkg/onepassword/containers.go
Normal file
63
operator/pkg/onepassword/containers.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/operator/pkg/apis/onepassword/v1"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/utils"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret) bool {
|
||||
for i := 0; i < len(containers); i++ {
|
||||
envVariables := containers[i].Env
|
||||
for j := 0; j < len(envVariables); j++ {
|
||||
if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil {
|
||||
_, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AppendUpdatedContainerSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret {
|
||||
for i := 0; i < len(containers); i++ {
|
||||
envVariables := containers[i].Env
|
||||
for j := 0; j < len(envVariables); j++ {
|
||||
if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil {
|
||||
secret, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name]
|
||||
if ok {
|
||||
updatedDeploymentSecrets[secret.Name] = secret
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return updatedDeploymentSecrets
|
||||
}
|
||||
|
||||
func AreContainersUsingInjectedSecrets(containers []corev1.Container, injectedContainers []string, items map[string]*onepasswordv1.OnePasswordItem) bool {
|
||||
for _, container := range containers {
|
||||
envVariables := container.Env
|
||||
|
||||
// check if container was set to be injected with secrets
|
||||
for _, injectedContainer := range injectedContainers {
|
||||
if injectedContainer != container.Name {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// check if any environment variables are using an updated injected secret
|
||||
for _, envVariable := range envVariables {
|
||||
referenceVault, referenceItem, err := ParseReference(envVariable.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
_, itemFound := items[utils.BuildInjectedOnePasswordItemName(referenceVault, referenceItem)]
|
||||
if itemFound {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@@ -2,12 +2,14 @@ package onepassword
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestAreContainersUsingSecrets(t *testing.T) {
|
||||
secretNamesToSearch := map[string]bool{
|
||||
"onepassword-database-secret": true,
|
||||
"onepassword-api-key": true,
|
||||
secretNamesToSearch := map[string]*corev1.Secret{
|
||||
"onepassword-database-secret": &corev1.Secret{},
|
||||
"onepassword-api-key": &corev1.Secret{},
|
||||
}
|
||||
|
||||
containerSecretNames := []string{
|
||||
@@ -24,9 +26,9 @@ func TestAreContainersUsingSecrets(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAreContainersNotUsingSecrets(t *testing.T) {
|
||||
secretNamesToSearch := map[string]bool{
|
||||
"onepassword-database-secret": true,
|
||||
"onepassword-api-key": true,
|
||||
secretNamesToSearch := map[string]*corev1.Secret{
|
||||
"onepassword-database-secret": &corev1.Secret{},
|
||||
"onepassword-api-key": &corev1.Secret{},
|
||||
}
|
||||
|
||||
containerSecretNames := []string{
|
40
operator/pkg/onepassword/deployments.go
Normal file
40
operator/pkg/onepassword/deployments.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/operator/pkg/apis/onepassword/v1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func IsDeploymentUsingSecrets(deployment *appsv1.Deployment, secrets map[string]*corev1.Secret) bool {
|
||||
volumes := deployment.Spec.Template.Spec.Volumes
|
||||
containers := deployment.Spec.Template.Spec.Containers
|
||||
containers = append(containers, deployment.Spec.Template.Spec.InitContainers...)
|
||||
return AreAnnotationsUsingSecrets(deployment.Annotations, secrets) || AreContainersUsingSecrets(containers, secrets) || AreVolumesUsingSecrets(volumes, secrets)
|
||||
}
|
||||
|
||||
func GetUpdatedSecretsForDeployment(deployment *appsv1.Deployment, secrets map[string]*corev1.Secret) map[string]*corev1.Secret {
|
||||
volumes := deployment.Spec.Template.Spec.Volumes
|
||||
containers := deployment.Spec.Template.Spec.Containers
|
||||
containers = append(containers, deployment.Spec.Template.Spec.InitContainers...)
|
||||
|
||||
updatedSecretsForDeployment := map[string]*corev1.Secret{}
|
||||
AppendAnnotationUpdatedSecret(deployment.Annotations, secrets, updatedSecretsForDeployment)
|
||||
AppendUpdatedContainerSecrets(containers, secrets, updatedSecretsForDeployment)
|
||||
AppendUpdatedVolumeSecrets(volumes, secrets, updatedSecretsForDeployment)
|
||||
|
||||
return updatedSecretsForDeployment
|
||||
}
|
||||
|
||||
func IsDeploymentUsingInjectedSecrets(deployment *appsv1.Deployment, items map[string]*onepasswordv1.OnePasswordItem) bool {
|
||||
containers := deployment.Spec.Template.Spec.Containers
|
||||
containers = append(containers, deployment.Spec.Template.Spec.InitContainers...)
|
||||
injectedContainers, enabled := deployment.Spec.Template.Annotations[ContainerInjectAnnotation]
|
||||
if !enabled {
|
||||
return false
|
||||
}
|
||||
parsedInjectedContainers := strings.Split(injectedContainers, ",")
|
||||
return AreContainersUsingInjectedSecrets(containers, parsedInjectedContainers, items)
|
||||
}
|
@@ -4,12 +4,13 @@ import (
|
||||
"testing"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestIsDeploymentUsingSecretsUsingVolumes(t *testing.T) {
|
||||
secretNamesToSearch := map[string]bool{
|
||||
"onepassword-database-secret": true,
|
||||
"onepassword-api-key": true,
|
||||
secretNamesToSearch := map[string]*corev1.Secret{
|
||||
"onepassword-database-secret": &corev1.Secret{},
|
||||
"onepassword-api-key": &corev1.Secret{},
|
||||
}
|
||||
|
||||
volumeSecretNames := []string{
|
||||
@@ -26,9 +27,9 @@ func TestIsDeploymentUsingSecretsUsingVolumes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsDeploymentUsingSecretsUsingContainers(t *testing.T) {
|
||||
secretNamesToSearch := map[string]bool{
|
||||
"onepassword-database-secret": true,
|
||||
"onepassword-api-key": true,
|
||||
secretNamesToSearch := map[string]*corev1.Secret{
|
||||
"onepassword-database-secret": &corev1.Secret{},
|
||||
"onepassword-api-key": &corev1.Secret{},
|
||||
}
|
||||
|
||||
containerSecretNames := []string{
|
||||
@@ -45,9 +46,9 @@ func TestIsDeploymentUsingSecretsUsingContainers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsDeploymentNotUSingSecrets(t *testing.T) {
|
||||
secretNamesToSearch := map[string]bool{
|
||||
"onepassword-database-secret": true,
|
||||
"onepassword-api-key": true,
|
||||
secretNamesToSearch := map[string]*corev1.Secret{
|
||||
"onepassword-database-secret": &corev1.Secret{},
|
||||
"onepassword-api-key": &corev1.Secret{},
|
||||
}
|
||||
|
||||
deployment := &appsv1.Deployment{}
|
118
operator/pkg/onepassword/items.go
Normal file
118
operator/pkg/onepassword/items.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
var logger = logf.Log.WithName("retrieve_item")
|
||||
|
||||
const secretReferencePrefix = "op://"
|
||||
|
||||
func GetOnePasswordItemByPath(opConnectClient connect.Client, path string) (*onepassword.Item, error) {
|
||||
vaultValue, itemValue, err := ParseVaultAndItemFromPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vaultId, err := getVaultId(opConnectClient, vaultValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
itemId, err := getItemId(opConnectClient, itemValue, vaultId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
item, err := opConnectClient.GetItem(itemId, vaultId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func ParseReference(reference string) (string, string, error) {
|
||||
if !strings.HasPrefix(reference, secretReferencePrefix) {
|
||||
return "", "", fmt.Errorf("secret reference should start with `op://`")
|
||||
}
|
||||
path := strings.TrimPrefix(reference, secretReferencePrefix)
|
||||
|
||||
splitPath := strings.Split(path, "/")
|
||||
if len(splitPath) != 3 {
|
||||
return "", "", fmt.Errorf("Invalid secret reference : %s. Secret references should match op://<vault>/<item>/<field>", reference)
|
||||
}
|
||||
|
||||
vault := splitPath[0]
|
||||
if vault == "" {
|
||||
return "", "", fmt.Errorf("Invalid secret reference : %s. Vault can't be empty.", reference)
|
||||
}
|
||||
|
||||
item := splitPath[1]
|
||||
if item == "" {
|
||||
return "", "", fmt.Errorf("Invalid secret reference : %s. Item can't be empty.", reference)
|
||||
}
|
||||
|
||||
return vault, item, nil
|
||||
}
|
||||
|
||||
func ParseVaultAndItemFromPath(path string) (string, string, error) {
|
||||
splitPath := strings.Split(path, "/")
|
||||
if len(splitPath) == 4 && splitPath[0] == "vaults" && splitPath[2] == "items" {
|
||||
return splitPath[1], splitPath[3], nil
|
||||
}
|
||||
return "", "", fmt.Errorf("%q is not an acceptable path for One Password item. Must be of the format: `vaults/{vault_id}/items/{item_id}`", path)
|
||||
}
|
||||
|
||||
func getVaultId(client connect.Client, vaultIdentifier string) (string, error) {
|
||||
if !IsValidClientUUID(vaultIdentifier) {
|
||||
vaults, err := client.GetVaultsByTitle(vaultIdentifier)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(vaults) == 0 {
|
||||
return "", fmt.Errorf("No vaults found with identifier %q", vaultIdentifier)
|
||||
}
|
||||
|
||||
oldestVault := vaults[0]
|
||||
if len(vaults) > 1 {
|
||||
for _, returnedVault := range vaults {
|
||||
if returnedVault.CreatedAt.Before(oldestVault.CreatedAt) {
|
||||
oldestVault = returnedVault
|
||||
}
|
||||
}
|
||||
logger.Info(fmt.Sprintf("%v 1Password vaults found with the title %q. Will use vault %q as it is the oldest.", len(vaults), vaultIdentifier, oldestVault.ID))
|
||||
}
|
||||
vaultIdentifier = oldestVault.ID
|
||||
}
|
||||
return vaultIdentifier, nil
|
||||
}
|
||||
|
||||
func getItemId(client connect.Client, itemIdentifier string, vaultId string) (string, error) {
|
||||
if !IsValidClientUUID(itemIdentifier) {
|
||||
items, err := client.GetItemsByTitle(itemIdentifier, vaultId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
return "", fmt.Errorf("No items found with identifier %q", itemIdentifier)
|
||||
}
|
||||
|
||||
oldestItem := items[0]
|
||||
if len(items) > 1 {
|
||||
for _, returnedItem := range items {
|
||||
if returnedItem.CreatedAt.Before(oldestItem.CreatedAt) {
|
||||
oldestItem = returnedItem
|
||||
}
|
||||
}
|
||||
logger.Info(fmt.Sprintf("%v 1Password items found with the title %q. Will use item %q as it is the oldest.", len(items), itemIdentifier, oldestItem.ID))
|
||||
}
|
||||
itemIdentifier = oldestItem.ID
|
||||
}
|
||||
return itemIdentifier, nil
|
||||
}
|
27
operator/pkg/onepassword/message/item_update.go
Normal file
27
operator/pkg/onepassword/message/item_update.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package message
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// TypeItemUpdate and others are sync message types
|
||||
const (
|
||||
TypeItemUpdate = "item.update"
|
||||
)
|
||||
|
||||
// ItemUpdateEvent is the data for a sync status message
|
||||
type ItemUpdateEvent struct {
|
||||
VaultUUID string `json:"vault_uuid"`
|
||||
ItemUUID string `json:"item_uuid"`
|
||||
ItemVersion string `json:"item_version"`
|
||||
}
|
||||
|
||||
// Type returns a the syns status data type
|
||||
func (s *ItemUpdateEvent) Type() string {
|
||||
return TypeItemUpdate
|
||||
}
|
||||
|
||||
// Bytes returns Bytes
|
||||
func (s *ItemUpdateEvent) Bytes() []byte {
|
||||
bytes, _ := json.Marshal(s)
|
||||
|
||||
return bytes
|
||||
}
|
90
operator/pkg/onepassword/onepassword_item.go
Normal file
90
operator/pkg/onepassword/onepassword_item.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/operator/pkg/apis/onepassword/v1"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/utils"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func CreateOnePasswordItemResourceFromDeployment(opClient connect.Client, kubeClient kubernetesClient.Client, deployment *appsv1.Deployment, injectedContainers []string) error {
|
||||
containers := deployment.Spec.Template.Spec.Containers
|
||||
containers = append(containers, deployment.Spec.Template.Spec.InitContainers...)
|
||||
for _, container := range containers {
|
||||
// check if container is listed is one of the containers
|
||||
// set to have injected secrets
|
||||
for _, injectedContainer := range injectedContainers {
|
||||
if injectedContainer != container.Name {
|
||||
continue
|
||||
}
|
||||
// create a one password item custom resource to track updates for injected secrets
|
||||
err := CreateOnePasswordCRSecretsFromContainer(opClient, kubeClient, container, deployment.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateOnePasswordCRSecretsFromContainer(opClient connect.Client, kubeClient kubernetesClient.Client, container corev1.Container, namespace string) error {
|
||||
for _, env := range container.Env {
|
||||
// if value is not of format op://<vault>/<item>/<field> then ignore
|
||||
vault, item, err := ParseReference(env.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// create a one password item custom resource to track updates for injected secrets
|
||||
err = CreateOnePasswordCRSecretFromReference(opClient, kubeClient, vault, item, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateOnePasswordCRSecretFromReference(opClient connect.Client, kubeClient kubernetesClient.Client, vault, item, namespace string) error {
|
||||
|
||||
retrievedItem, err := GetOnePasswordItemByPath(opClient, fmt.Sprintf("vaults/%s/items/%s", vault, item))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to retrieve item: %v", err)
|
||||
}
|
||||
|
||||
name := utils.BuildInjectedOnePasswordItemName(vault, item)
|
||||
onepassworditem := BuildOnePasswordItemCRFromPath(vault, item, name, namespace, fmt.Sprint(retrievedItem.Version))
|
||||
|
||||
currentOnepassworditem := &onepasswordv1.OnePasswordItem{}
|
||||
err = kubeClient.Get(context.Background(), types.NamespacedName{Name: onepassworditem.Name, Namespace: onepassworditem.Namespace}, currentOnepassworditem)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
log.Info(fmt.Sprintf("Creating OnePasswordItem CR %v at namespace '%v'", onepassworditem.Name, onepassworditem.Namespace))
|
||||
return kubeClient.Create(context.Background(), onepassworditem)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuildOnePasswordItemCRFromPath(vault, item, name, namespace, version string) *onepasswordv1.OnePasswordItem {
|
||||
return &onepasswordv1.OnePasswordItem{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
InjectedAnnotation: "true",
|
||||
VersionAnnotation: version,
|
||||
},
|
||||
},
|
||||
Spec: onepasswordv1.OnePasswordItemSpec{
|
||||
ItemPath: fmt.Sprintf("vaults/%s/items/%s", vault, item),
|
||||
},
|
||||
}
|
||||
}
|
291
operator/pkg/onepassword/secret_update_handler.go
Normal file
291
operator/pkg/onepassword/secret_update_handler.go
Normal file
@@ -0,0 +1,291 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/operator/pkg/apis/onepassword/v1"
|
||||
kubeSecrets "github.com/1Password/onepassword-operator/operator/pkg/kubernetessecrets"
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/utils"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
const envHostVariable = "OP_HOST"
|
||||
const lockTag = "operator.1password.io:ignore-secret"
|
||||
|
||||
var log = logf.Log.WithName("update_op_kubernetes_secrets_task")
|
||||
|
||||
func NewManager(kubernetesClient client.Client, opConnectClient connect.Client, shouldAutoRestartDeploymentsGlobal bool) *SecretUpdateHandler {
|
||||
return &SecretUpdateHandler{
|
||||
client: kubernetesClient,
|
||||
opConnectClient: opConnectClient,
|
||||
shouldAutoRestartDeploymentsGlobal: shouldAutoRestartDeploymentsGlobal,
|
||||
}
|
||||
}
|
||||
|
||||
type SecretUpdateHandler struct {
|
||||
client client.Client
|
||||
opConnectClient connect.Client
|
||||
shouldAutoRestartDeploymentsGlobal bool
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) UpdateKubernetesSecretsTask(vaultId, itemId string) error {
|
||||
updatedKubernetesSecrets, err := h.updateKubernetesSecrets(vaultId, itemId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedInjectedSecrets, err := h.updateInjectedSecrets(vaultId, itemId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return h.restartDeploymentsWithUpdatedSecrets(updatedKubernetesSecrets, updatedInjectedSecrets)
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecretsByNamespace map[string]map[string]*corev1.Secret, updatedInjectedSecretsByNamespace map[string]map[string]*onepasswordv1.OnePasswordItem) error {
|
||||
|
||||
if len(updatedSecretsByNamespace) == 0 && len(updatedInjectedSecretsByNamespace) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
deployments := &appsv1.DeploymentList{}
|
||||
err := h.client.List(context.Background(), deployments)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to list kubernetes deployments")
|
||||
return err
|
||||
}
|
||||
|
||||
if len(deployments.Items) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
setForAutoRestartByNamespaceMap, err := h.getIsSetForAutoRestartByNamespaceMap()
|
||||
if err != nil {
|
||||
log.Error(err, "Error determining which namespaces allow restarts")
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < len(deployments.Items); i++ {
|
||||
deployment := &deployments.Items[i]
|
||||
updatedSecrets := updatedSecretsByNamespace[deployment.Namespace]
|
||||
|
||||
// check if deployment is using one of the updated secrets
|
||||
updatedDeploymentSecrets := GetUpdatedSecretsForDeployment(deployment, updatedSecrets)
|
||||
if len(updatedDeploymentSecrets) != 0 {
|
||||
for _, secret := range updatedDeploymentSecrets {
|
||||
if isSecretSetForAutoRestart(secret, deployment, setForAutoRestartByNamespaceMap) {
|
||||
h.restartDeployment(deployment)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if the deployment is using one of the updated injected secrets
|
||||
updatedInjection := IsDeploymentUsingInjectedSecrets(deployment, updatedInjectedSecretsByNamespace[deployment.Namespace])
|
||||
if updatedInjection && isDeploymentSetForAutoRestart(deployment, setForAutoRestartByNamespaceMap) {
|
||||
h.restartDeployment(deployment)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("Deployment %q at namespace %q is up to date", deployment.GetName(), deployment.Namespace))
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) restartDeployment(deployment *appsv1.Deployment) {
|
||||
log.Info(fmt.Sprintf("Deployment %q at namespace %q references an updated secret. Restarting", deployment.GetName(), deployment.Namespace))
|
||||
if deployment.Spec.Template.Annotations == nil {
|
||||
deployment.Spec.Template.Annotations = map[string]string{}
|
||||
}
|
||||
deployment.Spec.Template.Annotations[RestartAnnotation] = time.Now().String()
|
||||
err := h.client.Update(context.Background(), deployment)
|
||||
if err != nil {
|
||||
log.Error(err, "Problem restarting deployment")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) updateKubernetesSecrets(vaultId, itemId string) (map[string]map[string]*corev1.Secret, error) {
|
||||
secrets := &corev1.SecretList{}
|
||||
err := h.client.List(context.Background(), secrets)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to list kubernetes secrets")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedSecrets := map[string]map[string]*corev1.Secret{}
|
||||
for i := 0; i < len(secrets.Items); i++ {
|
||||
secret := secrets.Items[i]
|
||||
|
||||
itemPath := secret.Annotations[ItemPathAnnotation]
|
||||
|
||||
currentVersion := secret.Annotations[VersionAnnotation]
|
||||
if len(itemPath) == 0 || len(currentVersion) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if vaultId != "" && itemId != "" && itemPath != fmt.Sprintf("vaults/%s/items%s", vaultId, itemId) {
|
||||
continue
|
||||
}
|
||||
|
||||
item, err := GetOnePasswordItemByPath(h.opConnectClient, secret.Annotations[ItemPathAnnotation])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to retrieve item: %v", err)
|
||||
}
|
||||
|
||||
itemVersion := fmt.Sprint(item.Version)
|
||||
if currentVersion != itemVersion {
|
||||
if isItemLockedForForcedRestarts(item) {
|
||||
log.Info(fmt.Sprintf("Secret '%v' has been updated in 1Password but is set to be ignored. Updates to an ignored secret will not trigger an update to a kubernetes secret or a rolling restart.", secret.GetName()))
|
||||
secret.Annotations[VersionAnnotation] = itemVersion
|
||||
h.client.Update(context.Background(), &secret)
|
||||
continue
|
||||
}
|
||||
log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName()))
|
||||
secret.Annotations[VersionAnnotation] = itemVersion
|
||||
updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, secret.Labels, *item)
|
||||
h.client.Update(context.Background(), updatedSecret)
|
||||
if updatedSecrets[secret.Namespace] == nil {
|
||||
updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret)
|
||||
}
|
||||
updatedSecrets[secret.Namespace][secret.Name] = &secret
|
||||
}
|
||||
}
|
||||
return updatedSecrets, nil
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) updateInjectedSecrets(vaultId, itemId string) (map[string]map[string]*onepasswordv1.OnePasswordItem, error) {
|
||||
// fetch all onepassworditems
|
||||
onepasswordItems := &onepasswordv1.OnePasswordItemList{}
|
||||
err := h.client.List(context.Background(), onepasswordItems)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to list OneOasswordItems")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedItems := map[string]map[string]*onepasswordv1.OnePasswordItem{}
|
||||
for _, item := range onepasswordItems.Items {
|
||||
|
||||
// if onepassworditem was generated by injecting a secret into a deployment then ignore
|
||||
_, injected := item.Annotations[InjectedAnnotation]
|
||||
if !injected {
|
||||
continue
|
||||
}
|
||||
|
||||
itemPath := item.Spec.ItemPath
|
||||
currentVersion := item.Annotations[VersionAnnotation]
|
||||
if len(itemPath) == 0 || len(currentVersion) == 0 {
|
||||
continue
|
||||
}
|
||||
if vaultId != "" && itemId != "" && itemPath != fmt.Sprintf("vaults/%s/items%s", vaultId, itemId) {
|
||||
continue
|
||||
}
|
||||
|
||||
storedItem, err := GetOnePasswordItemByPath(h.opConnectClient, itemPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to retrieve item: %v", err)
|
||||
}
|
||||
|
||||
itemVersion := fmt.Sprint(storedItem.Version)
|
||||
if currentVersion != itemVersion {
|
||||
item.Annotations[VersionAnnotation] = itemVersion
|
||||
h.client.Update(context.Background(), &item)
|
||||
if isItemLockedForForcedRestarts(storedItem) {
|
||||
log.Info(fmt.Sprintf("Secret '%v' has been updated in 1Password but is set to be ignored. Updates to an ignored secret will not trigger an update to a OnePasswordItem secret or a rolling restart.", item.Name))
|
||||
continue
|
||||
}
|
||||
if updatedItems[item.Namespace] == nil {
|
||||
updatedItems[item.Namespace] = make(map[string]*onepasswordv1.OnePasswordItem)
|
||||
}
|
||||
updatedItems[item.Namespace][item.Name] = &item
|
||||
}
|
||||
}
|
||||
return updatedItems, nil
|
||||
}
|
||||
|
||||
func isItemLockedForForcedRestarts(item *onepassword.Item) bool {
|
||||
tags := item.Tags
|
||||
for i := 0; i < len(tags); i++ {
|
||||
if tags[i] == lockTag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isUpdatedSecret(secretName string, updatedSecrets map[string]*corev1.Secret) bool {
|
||||
_, ok := updatedSecrets[secretName]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap() (map[string]bool, error) {
|
||||
namespaces := &corev1.NamespaceList{}
|
||||
err := h.client.List(context.Background(), namespaces)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to list kubernetes namespaces")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
namespacesMap := map[string]bool{}
|
||||
|
||||
for _, namespace := range namespaces.Items {
|
||||
namespacesMap[namespace.Name] = h.isNamespaceSetToAutoRestart(&namespace)
|
||||
}
|
||||
return namespacesMap, nil
|
||||
}
|
||||
|
||||
func isSecretSetForAutoRestart(secret *corev1.Secret, deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool {
|
||||
restartDeployment := secret.Annotations[RestartDeploymentsAnnotation]
|
||||
//If annotation for auto restarts for deployment is not set. Check for the annotation on its deployment
|
||||
if restartDeployment == "" {
|
||||
return isDeploymentSetForAutoRestart(deployment, setForAutoRestartByNamespace)
|
||||
}
|
||||
|
||||
restartDeploymentBool, err := utils.StringToBool(restartDeployment)
|
||||
if err != nil {
|
||||
log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secret.Name)
|
||||
return false
|
||||
}
|
||||
return restartDeploymentBool
|
||||
}
|
||||
|
||||
func isDeploymentSetForAutoRestart(deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool {
|
||||
restartDeployment := deployment.Annotations[RestartDeploymentsAnnotation]
|
||||
//If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace
|
||||
if restartDeployment == "" {
|
||||
return setForAutoRestartByNamespace[deployment.Namespace]
|
||||
}
|
||||
|
||||
restartDeploymentBool, err := utils.StringToBool(restartDeployment)
|
||||
if err != nil {
|
||||
log.Error(err, "Error parsing %v annotation on Deployment %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, deployment.Name)
|
||||
return false
|
||||
}
|
||||
return restartDeploymentBool
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) isNamespaceSetToAutoRestart(namespace *corev1.Namespace) bool {
|
||||
restartDeployment := namespace.Annotations[RestartDeploymentsAnnotation]
|
||||
//If annotation for auto restarts for deployment is not set. Check environment variable set on the operator
|
||||
if restartDeployment == "" {
|
||||
return h.shouldAutoRestartDeploymentsGlobal
|
||||
}
|
||||
|
||||
restartDeploymentBool, err := utils.StringToBool(restartDeployment)
|
||||
if err != nil {
|
||||
log.Error(err, "Error parsing %v annotation on Namespace %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, namespace.Name)
|
||||
return false
|
||||
}
|
||||
return restartDeploymentBool
|
||||
}
|
984
operator/pkg/onepassword/secret_update_handler_test.go
Normal file
984
operator/pkg/onepassword/secret_update_handler_test.go
Normal file
@@ -0,0 +1,984 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/1Password/onepassword-operator/operator/pkg/mocks"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/operator/pkg/apis/onepassword/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
errors2 "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
const (
|
||||
deploymentKind = "Deployment"
|
||||
deploymentAPIVersion = "v1"
|
||||
name = "test-deployment"
|
||||
namespace = "default"
|
||||
vaultId = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
itemId = "nwrhuano7bcwddcviubpp4mhfq"
|
||||
username = "test-user"
|
||||
password = "QmHumKc$mUeEem7caHtbaBaJ"
|
||||
userKey = "username"
|
||||
passKey = "password"
|
||||
itemVersion = 123
|
||||
injectedOnePasswordItemName = "injectedsecret-" + vaultId + "-" + itemId
|
||||
)
|
||||
|
||||
type testUpdateSecretTask struct {
|
||||
testName string
|
||||
existingDeployment *appsv1.Deployment
|
||||
existingNamespace *corev1.Namespace
|
||||
existingOnePasswordItem *onepasswordv1.OnePasswordItem
|
||||
existingSecret *corev1.Secret
|
||||
expectedError error
|
||||
expectedResultSecret *corev1.Secret
|
||||
expectedEvents []string
|
||||
opItem map[string]string
|
||||
expectedRestart bool
|
||||
globalAutoRestartEnabled bool
|
||||
}
|
||||
|
||||
var (
|
||||
expectedSecretData = map[string][]byte{
|
||||
"password": []byte(password),
|
||||
"username": []byte(username),
|
||||
}
|
||||
itemPath = fmt.Sprintf("vaults/%v/items/%v", vaultId, itemId)
|
||||
)
|
||||
|
||||
var defaultNamespace = &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
},
|
||||
}
|
||||
|
||||
var tests = []testUpdateSecretTask{
|
||||
{
|
||||
testName: "Test unrelated deployment is not restarted with an updated secret",
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
NameAnnotation: "unlrelated secret",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: false,
|
||||
globalAutoRestartEnabled: true,
|
||||
},
|
||||
{
|
||||
testName: "OP item has new version. Secret needs update. Deployment is restarted based on containers",
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: name,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Key: passKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: true,
|
||||
globalAutoRestartEnabled: true,
|
||||
},
|
||||
{
|
||||
testName: "OP item has new version. Secret needs update. Deployment is restarted based on annotation",
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
ItemPathAnnotation: itemPath,
|
||||
NameAnnotation: name,
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: true,
|
||||
globalAutoRestartEnabled: true,
|
||||
},
|
||||
{
|
||||
testName: "OP item has new version. Secret needs update. Deployment is restarted based on volume",
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: name,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: true,
|
||||
globalAutoRestartEnabled: true,
|
||||
},
|
||||
{
|
||||
testName: "No secrets need update. No deployment is restarted",
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
ItemPathAnnotation: itemPath,
|
||||
NameAnnotation: name,
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: false,
|
||||
globalAutoRestartEnabled: true,
|
||||
},
|
||||
{
|
||||
testName: `Deployment is not restarted when no auto restart is set to true for all
|
||||
deployments and is not overwritten by by a namespace or deployment annotation`,
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: name,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Key: passKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: false,
|
||||
globalAutoRestartEnabled: false,
|
||||
},
|
||||
{
|
||||
testName: `Secret autostart true value takes precedence over false deployment value`,
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
RestartDeploymentsAnnotation: "false",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: name,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Key: passKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
RestartDeploymentsAnnotation: "true",
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
RestartDeploymentsAnnotation: "true",
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: true,
|
||||
globalAutoRestartEnabled: false,
|
||||
},
|
||||
{
|
||||
testName: `Secret autostart true value takes precedence over false deployment value`,
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
RestartDeploymentsAnnotation: "true",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: name,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Key: passKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
RestartDeploymentsAnnotation: "false",
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
RestartDeploymentsAnnotation: "false",
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: false,
|
||||
globalAutoRestartEnabled: true,
|
||||
},
|
||||
{
|
||||
testName: `Deployment autostart true value takes precedence over false global auto restart value`,
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
RestartDeploymentsAnnotation: "true",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: name,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Key: passKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: true,
|
||||
globalAutoRestartEnabled: false,
|
||||
},
|
||||
{
|
||||
testName: `Deployment autostart false value takes precedence over false global auto restart value,
|
||||
and true namespace value.`,
|
||||
existingNamespace: &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
Annotations: map[string]string{
|
||||
RestartDeploymentsAnnotation: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
RestartDeploymentsAnnotation: "false",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: name,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Key: passKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: false,
|
||||
globalAutoRestartEnabled: false,
|
||||
},
|
||||
{
|
||||
testName: `Namespace autostart true value takes precedence over false global auto restart value`,
|
||||
existingNamespace: &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
Annotations: map[string]string{
|
||||
RestartDeploymentsAnnotation: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: name,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Key: passKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: true,
|
||||
globalAutoRestartEnabled: false,
|
||||
},
|
||||
{
|
||||
testName: "OP item has new version. Secret needs update. Deployment is restarted based on injected secrets in containers",
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
ContainerInjectAnnotation: "test-app",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "test-app",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: name,
|
||||
Value: fmt.Sprintf("op://%s/%s/test", vaultId, itemId),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingOnePasswordItem: &onepasswordv1.OnePasswordItem{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "OnePasswordItem",
|
||||
APIVersion: "onepassword.com/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: injectedOnePasswordItemName,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
InjectedAnnotation: "true",
|
||||
VersionAnnotation: "old",
|
||||
},
|
||||
},
|
||||
Spec: onepasswordv1.OnePasswordItemSpec{
|
||||
ItemPath: itemPath,
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: true,
|
||||
globalAutoRestartEnabled: true,
|
||||
},
|
||||
{
|
||||
testName: "OP item has new version. Secret needs update. Deployment does not have a inject annotation",
|
||||
existingNamespace: defaultNamespace,
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "test-app",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: name,
|
||||
Value: fmt.Sprintf("op://%s/%s/test", vaultId, itemId),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingOnePasswordItem: &onepasswordv1.OnePasswordItem{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "OnePasswordItem",
|
||||
APIVersion: "onepassword.com/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-%s", vaultId, itemId),
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
InjectedAnnotation: "true",
|
||||
VersionAnnotation: "old",
|
||||
},
|
||||
},
|
||||
Spec: onepasswordv1.OnePasswordItemSpec{
|
||||
ItemPath: itemPath,
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: false,
|
||||
globalAutoRestartEnabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
func TestUpdateSecretHandler(t *testing.T) {
|
||||
for _, testData := range tests {
|
||||
t.Run(testData.testName, func(t *testing.T) {
|
||||
|
||||
// Register operator types with the runtime scheme.
|
||||
s := scheme.Scheme
|
||||
s.AddKnownTypes(appsv1.SchemeGroupVersion, &onepasswordv1.OnePasswordItem{}, &onepasswordv1.OnePasswordItemList{}, &appsv1.Deployment{})
|
||||
|
||||
// Objects to track in the fake client.
|
||||
objs := []runtime.Object{
|
||||
testData.existingDeployment,
|
||||
testData.existingNamespace,
|
||||
}
|
||||
|
||||
if testData.existingSecret != nil {
|
||||
objs = append(objs, testData.existingSecret)
|
||||
}
|
||||
|
||||
if testData.existingOnePasswordItem != nil {
|
||||
objs = append(objs, testData.existingOnePasswordItem)
|
||||
}
|
||||
|
||||
// Create a fake client to mock API calls.
|
||||
cl := fake.NewFakeClientWithScheme(s, objs...)
|
||||
|
||||
opConnectClient := &mocks.TestClient{}
|
||||
mocks.GetGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
|
||||
item := onepassword.Item{}
|
||||
item.Fields = generateFields(testData.opItem["username"], testData.opItem["password"])
|
||||
item.Version = itemVersion
|
||||
item.Vault.ID = vaultUUID
|
||||
item.ID = uuid
|
||||
return &item, nil
|
||||
}
|
||||
h := &SecretUpdateHandler{
|
||||
client: cl,
|
||||
opConnectClient: opConnectClient,
|
||||
shouldAutoRestartDeploymentsGlobal: testData.globalAutoRestartEnabled,
|
||||
}
|
||||
|
||||
err := h.UpdateKubernetesSecretsTask("", "")
|
||||
|
||||
assert.Equal(t, testData.expectedError, err)
|
||||
|
||||
var expectedSecretName string
|
||||
if testData.expectedResultSecret == nil {
|
||||
expectedSecretName = testData.existingDeployment.Name
|
||||
} else {
|
||||
expectedSecretName = testData.expectedResultSecret.Name
|
||||
}
|
||||
|
||||
// Check if Secret has been created and has the correct data
|
||||
secret := &corev1.Secret{}
|
||||
err = cl.Get(context.TODO(), types.NamespacedName{Name: expectedSecretName, Namespace: namespace}, secret)
|
||||
|
||||
if testData.expectedResultSecret == nil {
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors2.IsNotFound(err))
|
||||
} else {
|
||||
assert.Equal(t, testData.expectedResultSecret.Data, secret.Data)
|
||||
assert.Equal(t, testData.expectedResultSecret.Name, secret.Name)
|
||||
assert.Equal(t, testData.expectedResultSecret.Type, secret.Type)
|
||||
assert.Equal(t, testData.expectedResultSecret.Annotations[VersionAnnotation], secret.Annotations[VersionAnnotation])
|
||||
}
|
||||
|
||||
//check if deployment has been restarted
|
||||
deployment := &appsv1.Deployment{}
|
||||
err = cl.Get(context.TODO(), types.NamespacedName{Name: testData.existingDeployment.Name, Namespace: namespace}, deployment)
|
||||
|
||||
_, ok := deployment.Spec.Template.Annotations[RestartAnnotation]
|
||||
if ok {
|
||||
assert.True(t, testData.expectedRestart, "Deployment was restarted but should not have been.")
|
||||
} else {
|
||||
assert.False(t, testData.expectedRestart, "Expected deployment to restart but it did not")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUpdatedSecret(t *testing.T) {
|
||||
|
||||
secretName := "test-secret"
|
||||
updatedSecrets := map[string]*corev1.Secret{
|
||||
"some_secret": &corev1.Secret{},
|
||||
}
|
||||
assert.False(t, isUpdatedSecret(secretName, updatedSecrets))
|
||||
|
||||
updatedSecrets[secretName] = &corev1.Secret{}
|
||||
assert.True(t, isUpdatedSecret(secretName, updatedSecrets))
|
||||
}
|
||||
|
||||
func generateFields(username, password string) []*onepassword.ItemField {
|
||||
fields := []*onepassword.ItemField{
|
||||
{
|
||||
Label: "username",
|
||||
Value: username,
|
||||
},
|
||||
{
|
||||
Label: "password",
|
||||
Value: password,
|
||||
},
|
||||
}
|
||||
return fields
|
||||
}
|
20
operator/pkg/onepassword/uuid.go
Normal file
20
operator/pkg/onepassword/uuid.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package onepassword
|
||||
|
||||
// UUIDLength defines the required length of UUIDs
|
||||
const UUIDLength = 26
|
||||
|
||||
// IsValidClientUUID returns true if the given client uuid is valid.
|
||||
func IsValidClientUUID(uuid string) bool {
|
||||
if len(uuid) != UUIDLength {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range uuid {
|
||||
valid := (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
|
||||
if !valid {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
29
operator/pkg/onepassword/volumes.go
Normal file
29
operator/pkg/onepassword/volumes.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package onepassword
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
func AreVolumesUsingSecrets(volumes []corev1.Volume, secrets map[string]*corev1.Secret) bool {
|
||||
for i := 0; i < len(volumes); i++ {
|
||||
if secret := volumes[i].Secret; secret != nil {
|
||||
secretName := secret.SecretName
|
||||
_, ok := secrets[secretName]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AppendUpdatedVolumeSecrets(volumes []corev1.Volume, secrets map[string]*corev1.Secret, updatedDeploymentSecrets map[string]*corev1.Secret) map[string]*corev1.Secret {
|
||||
for i := 0; i < len(volumes); i++ {
|
||||
if secret := volumes[i].Secret; secret != nil {
|
||||
secretName := secret.SecretName
|
||||
secret, ok := secrets[secretName]
|
||||
if ok {
|
||||
updatedDeploymentSecrets[secret.Name] = secret
|
||||
}
|
||||
}
|
||||
}
|
||||
return updatedDeploymentSecrets
|
||||
}
|
@@ -2,12 +2,14 @@ package onepassword
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestAreVolmesUsingSecrets(t *testing.T) {
|
||||
secretNamesToSearch := map[string]bool{
|
||||
"onepassword-database-secret": true,
|
||||
"onepassword-api-key": true,
|
||||
secretNamesToSearch := map[string]*corev1.Secret{
|
||||
"onepassword-database-secret": &corev1.Secret{},
|
||||
"onepassword-api-key": &corev1.Secret{},
|
||||
}
|
||||
|
||||
volumeSecretNames := []string{
|
||||
@@ -24,9 +26,9 @@ func TestAreVolmesUsingSecrets(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAreVolumesNotUsingSecrets(t *testing.T) {
|
||||
secretNamesToSearch := map[string]bool{
|
||||
"onepassword-database-secret": true,
|
||||
"onepassword-api-key": true,
|
||||
secretNamesToSearch := map[string]*corev1.Secret{
|
||||
"onepassword-database-secret": &corev1.Secret{},
|
||||
"onepassword-api-key": &corev1.Secret{},
|
||||
}
|
||||
|
||||
volumeSecretNames := []string{
|
66
operator/pkg/utils/string.go
Normal file
66
operator/pkg/utils/string.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
kubeValidate "k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
var invalidDNS1123Chars = regexp.MustCompile("[^a-z0-9-.]+")
|
||||
|
||||
func ContainsString(slice []string, s string) bool {
|
||||
for _, item := range slice {
|
||||
if item == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RemoveString(slice []string, s string) (result []string) {
|
||||
for _, item := range slice {
|
||||
if item == s {
|
||||
continue
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func StringToBool(str string) (bool, error) {
|
||||
restartDeploymentBool, err := strconv.ParseBool(strings.ToLower(str))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return restartDeploymentBool, nil
|
||||
}
|
||||
|
||||
// formatSecretName rewrites a value to be a valid Secret name.
|
||||
//
|
||||
// The Secret meta.name and data keys must be valid DNS subdomain names
|
||||
// (https://kubernetes.io/docs/concepts/configuration/secret/#overview-of-secrets)
|
||||
func FormatSecretName(value string) string {
|
||||
if errs := kubeValidate.IsDNS1123Subdomain(value); len(errs) == 0 {
|
||||
return value
|
||||
}
|
||||
return CreateValidSecretName(value)
|
||||
}
|
||||
|
||||
func CreateValidSecretName(value string) string {
|
||||
result := strings.ToLower(value)
|
||||
result = invalidDNS1123Chars.ReplaceAllString(result, "-")
|
||||
|
||||
if len(result) > kubeValidate.DNS1123SubdomainMaxLength {
|
||||
result = result[0:kubeValidate.DNS1123SubdomainMaxLength]
|
||||
}
|
||||
|
||||
// first and last character MUST be alphanumeric
|
||||
return strings.Trim(result, "-.")
|
||||
}
|
||||
|
||||
func BuildInjectedOnePasswordItemName(vaultId, injectedId string) string {
|
||||
return FormatSecretName(fmt.Sprintf("injectedsecret-%s-%s", vaultId, injectedId))
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
package kubernetessecrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
const onepasswordPrefix = "onepasswordoperator"
|
||||
const NameAnnotation = onepasswordPrefix + "/item-name"
|
||||
const VersionAnnotation = onepasswordPrefix + "/item-version"
|
||||
const restartAnnotation = onepasswordPrefix + "/lastRestarted"
|
||||
const ItemPathAnnotation = onepasswordPrefix + "/item-path"
|
||||
|
||||
var log = logf.Log
|
||||
|
||||
func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item) error {
|
||||
|
||||
itemVersion := fmt.Sprint(item.Version)
|
||||
annotations := map[string]string{
|
||||
VersionAnnotation: itemVersion,
|
||||
ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID),
|
||||
}
|
||||
secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, annotations, *item)
|
||||
|
||||
currentSecret := &corev1.Secret{}
|
||||
err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
log.Info(fmt.Sprintf("Creating Secret %v at namespace '%v'", secret.Name, secret.Namespace))
|
||||
return kubeClient.Create(context.Background(), secret)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentSecret.Annotations[VersionAnnotation] != itemVersion {
|
||||
log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace))
|
||||
currentSecret.ObjectMeta.Annotations = annotations
|
||||
currentSecret.Data = secret.Data
|
||||
return kubeClient.Update(context.Background(), currentSecret)
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("Secret with name %v and version %v already exists", secret.Name, secret.Annotations[VersionAnnotation]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, item onepassword.Item) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: annotations,
|
||||
},
|
||||
Data: BuildKubernetesSecretData(item.Fields),
|
||||
}
|
||||
}
|
||||
|
||||
func BuildKubernetesSecretData(fields []*onepassword.ItemField) map[string][]byte {
|
||||
secretData := map[string][]byte{}
|
||||
for i := 0; i < len(fields); i++ {
|
||||
if fields[i].Value != "" {
|
||||
secretData[fields[i].Label] = []byte(fields[i].Value)
|
||||
}
|
||||
}
|
||||
return secretData
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
)
|
||||
|
||||
type TestClient struct {
|
||||
GetVaultsFunc func() ([]onepassword.Vault, error)
|
||||
GetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error)
|
||||
GetItemsFunc func(vaultUUID string) ([]onepassword.Item, error)
|
||||
GetItemByTitleFunc func(title string, vaultUUID string) (*onepassword.Item, error)
|
||||
CreateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
UpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
DeleteItemFunc func(item *onepassword.Item, vaultUUID string) error
|
||||
}
|
||||
|
||||
var (
|
||||
GetGetVaultsFunc func() ([]onepassword.Vault, error)
|
||||
GetGetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error)
|
||||
DoGetItemByTitleFunc func(title string, vaultUUID string) (*onepassword.Item, error)
|
||||
DoCreateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
DoDeleteItemFunc func(item *onepassword.Item, vaultUUID string) error
|
||||
DoGetItemsFunc func(vaultUUID string) ([]onepassword.Item, error)
|
||||
DoUpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
)
|
||||
|
||||
// Do is the mock client's `Do` func
|
||||
func (m *TestClient) GetVaults() ([]onepassword.Vault, error) {
|
||||
return GetGetVaultsFunc()
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
return GetGetItemFunc(uuid, vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItems(vaultUUID string) ([]onepassword.Item, error) {
|
||||
return DoGetItemsFunc(vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItemByTitle(title string, vaultUUID string) (*onepassword.Item, error) {
|
||||
return DoGetItemByTitleFunc(title, vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) CreateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) {
|
||||
return DoCreateItemFunc(item, vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) DeleteItem(item *onepassword.Item, vaultUUID string) error {
|
||||
return DoDeleteItemFunc(item, vaultUUID)
|
||||
}
|
||||
|
||||
func (m *TestClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) {
|
||||
return DoUpdateItemFunc(item, vaultUUID)
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
OnepasswordPrefix = "onepasswordoperator"
|
||||
ItemPathAnnotation = OnepasswordPrefix + "/item-path"
|
||||
NameAnnotation = OnepasswordPrefix + "/item-name"
|
||||
VersionAnnotation = OnepasswordPrefix + "/item-version"
|
||||
RestartAnnotation = OnepasswordPrefix + "/lastRestarted"
|
||||
)
|
||||
|
||||
func GetAnnotationsForDeployment(deployment *appsv1.Deployment, regex *regexp.Regexp) (map[string]string, bool) {
|
||||
annotationsFound := false
|
||||
annotations := FilterAnnotations(deployment.Annotations, regex)
|
||||
if len(annotations) > 0 {
|
||||
annotationsFound = true
|
||||
} else {
|
||||
annotations = FilterAnnotations(deployment.Spec.Template.Annotations, regex)
|
||||
if len(annotations) > 0 {
|
||||
annotationsFound = true
|
||||
} else {
|
||||
annotationsFound = false
|
||||
}
|
||||
}
|
||||
|
||||
return annotations, annotationsFound
|
||||
}
|
||||
|
||||
func FilterAnnotations(annotations map[string]string, regex *regexp.Regexp) map[string]string {
|
||||
filteredAnnotations := make(map[string]string)
|
||||
for key, value := range annotations {
|
||||
if regex.MatchString(key) {
|
||||
filteredAnnotations[key] = value
|
||||
}
|
||||
}
|
||||
return filteredAnnotations
|
||||
}
|
||||
|
||||
func AreAnnotationsUsingSecrets(annotations map[string]string, secrets map[string]bool) bool {
|
||||
_, ok := secrets[annotations[NameAnnotation]]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string]bool) bool {
|
||||
for i := 0; i < len(containers); i++ {
|
||||
envVariables := containers[i].Env
|
||||
for j := 0; j < len(envVariables); j++ {
|
||||
if envVariables[j].ValueFrom != nil && envVariables[j].ValueFrom.SecretKeyRef != nil {
|
||||
_, ok := secrets[envVariables[j].ValueFrom.SecretKeyRef.Name]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import appsv1 "k8s.io/api/apps/v1"
|
||||
|
||||
func IsDeploymentUsingSecrets(deployment *appsv1.Deployment, secrets map[string]bool) bool {
|
||||
volumes := deployment.Spec.Template.Spec.Volumes
|
||||
containers := deployment.Spec.Template.Spec.Containers
|
||||
containers = append(containers, deployment.Spec.Template.Spec.InitContainers...)
|
||||
return AreAnnotationsUsingSecrets(deployment.Annotations, secrets) || AreContainersUsingSecrets(containers, secrets) || AreVolumesUsingSecrets(volumes, secrets)
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
)
|
||||
|
||||
func GetOnePasswordItemByPath(opConnectClient connect.Client, path string) (*onepassword.Item, error) {
|
||||
vaultId, itemId, err := ParseVaultIdAndItemIdFromPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item, err := opConnectClient.GetItem(itemId, vaultId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func ParseVaultIdAndItemIdFromPath(path string) (string, string, error) {
|
||||
splitPath := strings.Split(path, "/")
|
||||
if len(splitPath) == 4 && splitPath[0] == "vaults" && splitPath[2] == "items" {
|
||||
return splitPath[1], splitPath[3], nil
|
||||
}
|
||||
return "", "", fmt.Errorf("%q is not an acceptable path for One Password item. Must be of the format: `vaults/{vault_id}/items/{item_id}`", path)
|
||||
}
|
@@ -1,124 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
const envHostVariable = "OP_HOST"
|
||||
|
||||
var log = logf.Log.WithName("update_op_kubernetes_secrets_task")
|
||||
|
||||
func NewManager(kubernetesClient client.Client, opConnectClient connect.Client) *SecretUpdateHandler {
|
||||
return &SecretUpdateHandler{
|
||||
client: kubernetesClient,
|
||||
opConnectClient: opConnectClient,
|
||||
}
|
||||
}
|
||||
|
||||
type SecretUpdateHandler struct {
|
||||
client client.Client
|
||||
opConnectClient connect.Client
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) UpdateKubernetesSecretsTask() error {
|
||||
updatedKubernetesSecrets, err := h.updateKubernetesSecrets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return h.restartDeploymentsWithUpdatedSecrets(updatedKubernetesSecrets)
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecretsByNamespace map[string]map[string]bool) error {
|
||||
// No secrets to update. Exit
|
||||
if len(updatedSecretsByNamespace) == 0 || updatedSecretsByNamespace == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
deployments := &appsv1.DeploymentList{}
|
||||
err := h.client.List(context.Background(), deployments)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to list kubernetes deployments")
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < len(deployments.Items); i++ {
|
||||
deployment := &deployments.Items[i]
|
||||
updatedSecrets := updatedSecretsByNamespace[deployment.Namespace]
|
||||
secretName := deployment.Annotations[NameAnnotation]
|
||||
log.Info(fmt.Sprintf("Looking at secret %v for deployment %v", secretName, deployment.Name))
|
||||
if isUpdatedSecret(secretName, updatedSecrets) || IsDeploymentUsingSecrets(deployment, updatedSecrets) {
|
||||
h.restartDeployment(deployment)
|
||||
} else {
|
||||
log.Info(fmt.Sprintf("Deployment '%v' is up to date", deployment.GetName()))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) restartDeployment(deployment *appsv1.Deployment) {
|
||||
log.Info(fmt.Sprintf("Deployment '%v' references an updated secret. Restarting", deployment.GetName()))
|
||||
deployment.Spec.Template.Annotations = map[string]string{
|
||||
RestartAnnotation: time.Now().String(),
|
||||
}
|
||||
err := h.client.Update(context.Background(), deployment)
|
||||
if err != nil {
|
||||
log.Error(err, "Problem restarting deployment")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]bool, error) {
|
||||
secrets := &corev1.SecretList{}
|
||||
err := h.client.List(context.Background(), secrets)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to list kubernetes secrets")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedSecrets := map[string]map[string]bool{}
|
||||
for i := 0; i < len(secrets.Items); i++ {
|
||||
secret := secrets.Items[i]
|
||||
|
||||
itemPath := secret.Annotations[ItemPathAnnotation]
|
||||
currentVersion := secret.Annotations[VersionAnnotation]
|
||||
if len(itemPath) == 0 || len(currentVersion) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
item, err := GetOnePasswordItemByPath(h.opConnectClient, secret.Annotations[ItemPathAnnotation])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to retrieve item: %v", err)
|
||||
}
|
||||
|
||||
itemVersion := fmt.Sprint(item.Version)
|
||||
if currentVersion != itemVersion {
|
||||
log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName()))
|
||||
secret.Annotations[VersionAnnotation] = itemVersion
|
||||
updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, *item)
|
||||
h.client.Update(context.Background(), updatedSecret)
|
||||
if updatedSecrets[secret.Namespace] == nil {
|
||||
updatedSecrets[secret.Namespace] = make(map[string]bool)
|
||||
}
|
||||
updatedSecrets[secret.Namespace][secret.Name] = true
|
||||
}
|
||||
}
|
||||
return updatedSecrets, nil
|
||||
}
|
||||
|
||||
func isUpdatedSecret(secretName string, updatedSecrets map[string]bool) bool {
|
||||
_, ok := updatedSecrets[secretName]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
@@ -1,412 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/mocks"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
errors2 "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
const (
|
||||
deploymentKind = "Deployment"
|
||||
deploymentAPIVersion = "v1"
|
||||
name = "test-deployment"
|
||||
namespace = "default"
|
||||
vaultId = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
itemId = "nwrhuano7bcwddcviubpp4mhfq"
|
||||
username = "test-user"
|
||||
password = "QmHumKc$mUeEem7caHtbaBaJ"
|
||||
userKey = "username"
|
||||
passKey = "password"
|
||||
itemVersion = 123
|
||||
)
|
||||
|
||||
type testUpdateSecretTask struct {
|
||||
testName string
|
||||
existingDeployment *appsv1.Deployment
|
||||
existingSecret *corev1.Secret
|
||||
expectedError error
|
||||
expectedResultSecret *corev1.Secret
|
||||
expectedEvents []string
|
||||
opItem map[string]string
|
||||
expectedRestart bool
|
||||
}
|
||||
|
||||
var (
|
||||
expectedSecretData = map[string][]byte{
|
||||
"password": []byte(password),
|
||||
"username": []byte(username),
|
||||
}
|
||||
itemPath = fmt.Sprintf("vaults/%v/items/%v", vaultId, itemId)
|
||||
)
|
||||
|
||||
var tests = []testUpdateSecretTask{
|
||||
{
|
||||
testName: "Test unrelated deployment is not restarted with an updated secret",
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
NameAnnotation: "unlrelated secret",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: false,
|
||||
},
|
||||
{
|
||||
testName: "OP item has new version. Secret needs update. Deployment is restarted based on containers",
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: name,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Key: passKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: true,
|
||||
},
|
||||
{
|
||||
testName: "OP item has new version. Secret needs update. Deployment is restarted based on annotation",
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
ItemPathAnnotation: itemPath,
|
||||
NameAnnotation: name,
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: true,
|
||||
},
|
||||
{
|
||||
testName: "OP item has new version. Secret needs update. Deployment is restarted based on volume",
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: name,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: "old version",
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: true,
|
||||
},
|
||||
{
|
||||
testName: "No secrets need update. No deployment is restarted",
|
||||
existingDeployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: deploymentKind,
|
||||
APIVersion: deploymentAPIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
ItemPathAnnotation: itemPath,
|
||||
NameAnnotation: name,
|
||||
},
|
||||
},
|
||||
},
|
||||
existingSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
expectedError: nil,
|
||||
expectedResultSecret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: map[string]string{
|
||||
VersionAnnotation: fmt.Sprint(itemVersion),
|
||||
ItemPathAnnotation: itemPath,
|
||||
},
|
||||
},
|
||||
Data: expectedSecretData,
|
||||
},
|
||||
opItem: map[string]string{
|
||||
userKey: username,
|
||||
passKey: password,
|
||||
},
|
||||
expectedRestart: false,
|
||||
},
|
||||
}
|
||||
|
||||
func TestReconcileDepoyment(t *testing.T) {
|
||||
for _, testData := range tests {
|
||||
t.Run(testData.testName, func(t *testing.T) {
|
||||
|
||||
// Register operator types with the runtime scheme.
|
||||
s := scheme.Scheme
|
||||
s.AddKnownTypes(appsv1.SchemeGroupVersion, testData.existingDeployment)
|
||||
|
||||
// Objects to track in the fake client.
|
||||
objs := []runtime.Object{
|
||||
testData.existingDeployment,
|
||||
}
|
||||
|
||||
if testData.existingSecret != nil {
|
||||
objs = append(objs, testData.existingSecret)
|
||||
}
|
||||
|
||||
// Create a fake client to mock API calls.
|
||||
cl := fake.NewFakeClientWithScheme(s, objs...)
|
||||
|
||||
opConnectClient := &mocks.TestClient{}
|
||||
mocks.GetGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
|
||||
item := onepassword.Item{}
|
||||
item.Fields = generateFields(testData.opItem["username"], testData.opItem["password"])
|
||||
item.Version = itemVersion
|
||||
item.Vault.ID = vaultUUID
|
||||
item.ID = uuid
|
||||
return &item, nil
|
||||
}
|
||||
h := &SecretUpdateHandler{
|
||||
client: cl,
|
||||
opConnectClient: opConnectClient,
|
||||
}
|
||||
|
||||
err := h.UpdateKubernetesSecretsTask()
|
||||
|
||||
assert.Equal(t, testData.expectedError, err)
|
||||
|
||||
var expectedSecretName string
|
||||
if testData.expectedResultSecret == nil {
|
||||
expectedSecretName = testData.existingDeployment.Name
|
||||
} else {
|
||||
expectedSecretName = testData.expectedResultSecret.Name
|
||||
}
|
||||
|
||||
// Check if Secret has been created and has the correct data
|
||||
secret := &corev1.Secret{}
|
||||
err = cl.Get(context.TODO(), types.NamespacedName{Name: expectedSecretName, Namespace: namespace}, secret)
|
||||
|
||||
if testData.expectedResultSecret == nil {
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors2.IsNotFound(err))
|
||||
} else {
|
||||
assert.Equal(t, testData.expectedResultSecret.Data, secret.Data)
|
||||
assert.Equal(t, testData.expectedResultSecret.Name, secret.Name)
|
||||
assert.Equal(t, testData.expectedResultSecret.Type, secret.Type)
|
||||
assert.Equal(t, testData.expectedResultSecret.Annotations[VersionAnnotation], secret.Annotations[VersionAnnotation])
|
||||
}
|
||||
|
||||
//check if deployment has been restarted
|
||||
deployment := &appsv1.Deployment{}
|
||||
err = cl.Get(context.TODO(), types.NamespacedName{Name: testData.existingDeployment.Name, Namespace: namespace}, deployment)
|
||||
|
||||
_, ok := deployment.Spec.Template.Annotations[RestartAnnotation]
|
||||
if ok {
|
||||
assert.True(t, testData.expectedRestart)
|
||||
} else {
|
||||
assert.False(t, testData.expectedRestart)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUpdatedSecret(t *testing.T) {
|
||||
|
||||
secretName := "test-secret"
|
||||
updatedSecrets := map[string]bool{
|
||||
"some_secret": true,
|
||||
}
|
||||
assert.False(t, isUpdatedSecret(secretName, updatedSecrets))
|
||||
|
||||
updatedSecrets[secretName] = true
|
||||
assert.True(t, isUpdatedSecret(secretName, updatedSecrets))
|
||||
}
|
||||
|
||||
func generateFields(username, password string) []*onepassword.ItemField {
|
||||
fields := []*onepassword.ItemField{
|
||||
{
|
||||
Label: "username",
|
||||
Value: username,
|
||||
},
|
||||
{
|
||||
Label: "password",
|
||||
Value: password,
|
||||
},
|
||||
}
|
||||
return fields
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
func AreVolumesUsingSecrets(volumes []corev1.Volume, secrets map[string]bool) bool {
|
||||
for i := 0; i < len(volumes); i++ {
|
||||
if secret := volumes[i].Secret; secret != nil {
|
||||
secretName := secret.SecretName
|
||||
_, ok := secrets[secretName]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
package utils
|
||||
|
||||
func ContainsString(slice []string, s string) bool {
|
||||
for _, item := range slice {
|
||||
if item == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RemoveString(slice []string, s string) (result []string) {
|
||||
for _, item := range slice {
|
||||
if item == s {
|
||||
continue
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
return
|
||||
}
|
104
scripts/prepare-release.sh
Executable file
104
scripts/prepare-release.sh
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# prepare-release.sh
|
||||
# (Note: This should be called by `make release/prepare` because it depends
|
||||
# on several variables set by the Makefile)
|
||||
#
|
||||
# Performs release preparation tasks:
|
||||
# - Creates a release branch
|
||||
# - Renames "LATEST" section to the new version number
|
||||
# - Adds new "LATEST" entry to the changelog
|
||||
#
|
||||
##############################################
|
||||
set -Eeuo pipefail
|
||||
|
||||
if [[ -z "${NEW_VERSION:-}" ]]; then
|
||||
echo "[ERROR] NEW_VERSION environment variable not defined." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Script called from within a git repo?
|
||||
if [[ $(git rev-parse --is-inside-work-tree &>/dev/null) -ne 0 ]]; then
|
||||
echo "[ERROR] Current directory (${SRCDIR}) is not a git repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
CHANGELOG_FILENAME=${CHANGELOG:-"CHANGELOG.md"}
|
||||
|
||||
# normalize version by removing `v` prefix
|
||||
VERSION_NUM=${NEW_VERSION/#v/}
|
||||
RELEASE_BRANCH=$(printf "release/v%s" "${VERSION_NUM}")
|
||||
|
||||
function updateChangelog() {
|
||||
local tmpfile
|
||||
|
||||
trap '[ -e "${tmpfile}" ] && rm "${tmpfile}"' RETURN
|
||||
|
||||
local changelogFile
|
||||
changelogFile=$(printf "%s/%s" "${REPO_ROOT}" "${CHANGELOG_FILENAME}")
|
||||
|
||||
# create Changelog file if not exists
|
||||
if ! [[ -f "${REPO_ROOT}/${CHANGELOG_FILENAME}" ]]; then
|
||||
touch "${REPO_ROOT}/${CHANGELOG_FILENAME}" && \
|
||||
git add "${REPO_ROOT}/${CHANGELOG_FILENAME}"
|
||||
fi
|
||||
|
||||
tmpfile=$(mktemp)
|
||||
|
||||
# Replace "Latest" in the top-most changelog block with new version
|
||||
# Then push a new "latest" block to top of the changelog
|
||||
awk 'NR==1, /---/{ sub(/START\/LATEST/, "START/v'${VERSION_NUM}'"); sub(/# Latest/, "# v'${VERSION_NUM}'") } {print}' \
|
||||
"${changelogFile}" > "${tmpfile}"
|
||||
|
||||
# Inserts "Latest" changelog HEREDOC at the top of the file
|
||||
cat - "${tmpfile}" << EOF > "${REPO_ROOT}/${CHANGELOG_FILENAME}"
|
||||
[//]: # (START/LATEST)
|
||||
# Latest
|
||||
|
||||
## Features
|
||||
* A user-friendly description of a new feature. {issue-number}
|
||||
|
||||
## Fixes
|
||||
* A user-friendly description of a fix. {issue-number}
|
||||
|
||||
## Security
|
||||
* A user-friendly description of a security fix. {issue-number}
|
||||
|
||||
---
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
function _main() {
|
||||
|
||||
# Stash version changes
|
||||
git stash push &>/dev/null
|
||||
|
||||
if ! git checkout -b "${RELEASE_BRANCH}" origin/"${MAIN_BRANCH:-main}"; then
|
||||
echo "[ERROR] Could not check out release branch." >&2
|
||||
git stash pop &>/dev/null
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Add the version changes to release branch
|
||||
git stash pop &>/dev/null
|
||||
|
||||
updateChangelog
|
||||
|
||||
cat << EOF
|
||||
|
||||
[SUCCESS] Changelog updated & release branch created:
|
||||
New Version: ${NEW_VERSION}
|
||||
Release Branch: ${RELEASE_BRANCH}
|
||||
|
||||
Next steps:
|
||||
1. Edit the changelog notes in ${CHANGELOG_FILENAME}
|
||||
2. Commit changes to the release branch
|
||||
3. Push changes to remote => git push origin ${RELEASE_BRANCH}
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
_main
|
60
vendor/github.com/1Password/connect-sdk-go/connect/client.go
generated
vendored
60
vendor/github.com/1Password/connect-sdk-go/connect/client.go
generated
vendored
@@ -18,14 +18,16 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultUserAgent = "connect-sdk-go/0.0.1"
|
||||
defaultUserAgent = "connect-sdk-go/%s"
|
||||
)
|
||||
|
||||
// Client Represents an available 1Password Connect API to connect to
|
||||
type Client interface {
|
||||
GetVaults() ([]onepassword.Vault, error)
|
||||
GetVaultsByTitle(uuid string) ([]onepassword.Vault, error)
|
||||
GetItem(uuid string, vaultUUID string) (*onepassword.Item, error)
|
||||
GetItems(vaultUUID string) ([]onepassword.Item, error)
|
||||
GetItemsByTitle(title string, vaultUUID string) ([]onepassword.Item, error)
|
||||
GetItemByTitle(title string, vaultUUID string) (*onepassword.Item, error)
|
||||
CreateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error)
|
||||
@@ -59,7 +61,7 @@ func NewClientFromEnvironment() (Client, error) {
|
||||
|
||||
// NewClient Returns a Secret Service client for a given url and jwt
|
||||
func NewClient(url string, token string) Client {
|
||||
return NewClientWithUserAgent(url, token, defaultUserAgent)
|
||||
return NewClientWithUserAgent(url, token, fmt.Sprintf(defaultUserAgent, SDKVersion))
|
||||
}
|
||||
|
||||
// NewClientWithUserAgent Returns a Secret Service client for a given url and jwt and identifies with userAgent
|
||||
@@ -127,6 +129,39 @@ func (rs *restClient) GetVaults() ([]onepassword.Vault, error) {
|
||||
return vaults, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) {
|
||||
span := rs.tracer.StartSpan("GetVaultsByTitle")
|
||||
defer span.Finish()
|
||||
|
||||
filter := url.QueryEscape(fmt.Sprintf("title eq \"%s\"", title))
|
||||
itemURL := fmt.Sprintf("/v1/vaults?filter=%s", filter)
|
||||
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := rs.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, itemURL)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vaults := []onepassword.Vault{}
|
||||
if err := json.Unmarshal(body, &vaults); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vaults, nil
|
||||
}
|
||||
|
||||
// GetItem Get a specific Item from the 1Password Connect API
|
||||
func (rs *restClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
span := rs.tracer.StartSpan("GetItem")
|
||||
@@ -163,6 +198,21 @@ func (rs *restClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item,
|
||||
func (rs *restClient) GetItemByTitle(title string, vaultUUID string) (*onepassword.Item, error) {
|
||||
span := rs.tracer.StartSpan("GetItemByTitle")
|
||||
defer span.Finish()
|
||||
items, err := rs.GetItemsByTitle(title, vaultUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(items) != 1 {
|
||||
return nil, fmt.Errorf("Found %d item(s) in vault %q with title %q", len(items), vaultUUID, title)
|
||||
}
|
||||
|
||||
return rs.GetItem(items[0].ID, items[0].Vault.ID)
|
||||
}
|
||||
|
||||
func (rs *restClient) GetItemsByTitle(title string, vaultUUID string) ([]onepassword.Item, error) {
|
||||
span := rs.tracer.StartSpan("GetItemsByTitle")
|
||||
defer span.Finish()
|
||||
|
||||
filter := url.QueryEscape(fmt.Sprintf("title eq \"%s\"", title))
|
||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items?filter=%s", vaultUUID, filter)
|
||||
@@ -190,11 +240,7 @@ func (rs *restClient) GetItemByTitle(title string, vaultUUID string) (*onepasswo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(items) != 1 {
|
||||
return nil, fmt.Errorf("Found %d item(s) in vault %q with title %q", len(items), vaultUUID, title)
|
||||
}
|
||||
|
||||
return rs.GetItem(items[0].ID, items[0].Vault.ID)
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) GetItems(vaultUUID string) ([]onepassword.Item, error) {
|
||||
|
5
vendor/github.com/1Password/connect-sdk-go/connect/version.go
generated
vendored
Normal file
5
vendor/github.com/1Password/connect-sdk-go/connect/version.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package connect
|
||||
|
||||
// SDKVersion is the latest Semantic Version of the library
|
||||
// Do not rename this variable without changing the regex in the Makefile
|
||||
const SDKVersion = "1.0.1"
|
52
vendor/github.com/1Password/connect-sdk-go/onepassword/items.go
generated
vendored
52
vendor/github.com/1Password/connect-sdk-go/onepassword/items.go
generated
vendored
@@ -2,6 +2,7 @@ package onepassword
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -104,3 +105,54 @@ type ItemField struct {
|
||||
Recipe *GeneratorRecipe `json:"recipe,omitempty"`
|
||||
Entropy float64 `json:"entropy,omitempty"`
|
||||
}
|
||||
|
||||
// Get Retrieve the value of a field on the item by its label. To specify a
|
||||
// field from a specific section pass in <section label>.<field label>. If
|
||||
// no field matching the selector is found return "".
|
||||
func (i *Item) GetValue(field string) string {
|
||||
if i == nil || len(i.Fields) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
sectionFilter := false
|
||||
sectionLabel := ""
|
||||
fieldLabel := field
|
||||
if strings.Contains(field, ".") {
|
||||
parts := strings.Split(field, ".")
|
||||
|
||||
// Test to make sure the . isn't the last character
|
||||
if len(parts) == 2 {
|
||||
sectionFilter = true
|
||||
sectionLabel = parts[0]
|
||||
fieldLabel = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range i.Fields {
|
||||
if sectionFilter {
|
||||
if f.Section != nil {
|
||||
if sectionLabel != i.SectionLabelForID(f.Section.ID) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fieldLabel == f.Label {
|
||||
return f.Value
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (i *Item) SectionLabelForID(id string) string {
|
||||
if i != nil || len(i.Sections) > 0 {
|
||||
for _, s := range i.Sections {
|
||||
if s.ID == id {
|
||||
return s.Label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
4
vendor/github.com/google/uuid/hash.go
generated
vendored
4
vendor/github.com/google/uuid/hash.go
generated
vendored
@@ -26,8 +26,8 @@ var (
|
||||
// NewMD5 and NewSHA1.
|
||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||
h.Reset()
|
||||
h.Write(space[:])
|
||||
h.Write(data)
|
||||
h.Write(space[:]) //nolint:errcheck
|
||||
h.Write(data) //nolint:errcheck
|
||||
s := h.Sum(nil)
|
||||
var uuid UUID
|
||||
copy(uuid[:], s)
|
||||
|
118
vendor/github.com/google/uuid/null.go
generated
vendored
Normal file
118
vendor/github.com/google/uuid/null.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2021 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var jsonNull = []byte("null")
|
||||
|
||||
// NullUUID represents a UUID that may be null.
|
||||
// NullUUID implements the SQL driver.Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var u uuid.NullUUID
|
||||
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u)
|
||||
// ...
|
||||
// if u.Valid {
|
||||
// // use u.UUID
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
type NullUUID struct {
|
||||
UUID UUID
|
||||
Valid bool // Valid is true if UUID is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the SQL driver.Scanner interface.
|
||||
func (nu *NullUUID) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
nu.UUID, nu.Valid = Nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
err := nu.UUID.Scan(value)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nu NullUUID) Value() (driver.Value, error) {
|
||||
if !nu.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
// Delegate to UUID Value function
|
||||
return nu.UUID.Value()
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (nu NullUUID) MarshalBinary() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID[:], nil
|
||||
}
|
||||
|
||||
return []byte(nil), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(nu.UUID[:], data)
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (nu NullUUID) MarshalText() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID.MarshalText()
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
nu.UUID = id
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (nu NullUUID) MarshalJSON() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return json.Marshal(nu.UUID)
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (nu *NullUUID) UnmarshalJSON(data []byte) error {
|
||||
if bytes.Equal(data, jsonNull) {
|
||||
*nu = NullUUID{}
|
||||
return nil // valid null UUID
|
||||
}
|
||||
err := json.Unmarshal(data, &nu.UUID)
|
||||
nu.Valid = err == nil
|
||||
return err
|
||||
}
|
2
vendor/github.com/google/uuid/sql.go
generated
vendored
2
vendor/github.com/google/uuid/sql.go
generated
vendored
@@ -9,7 +9,7 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
|
||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
|
||||
// Currently, database types that map to string and []byte are supported. Please
|
||||
// consult database-specific driver documentation for matching types.
|
||||
func (uuid *UUID) Scan(src interface{}) error {
|
||||
|
55
vendor/github.com/google/uuid/uuid.go
generated
vendored
55
vendor/github.com/google/uuid/uuid.go
generated
vendored
@@ -12,6 +12,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||
@@ -33,7 +34,27 @@ const (
|
||||
Future // Reserved for future definition.
|
||||
)
|
||||
|
||||
var rander = rand.Reader // random function
|
||||
const randPoolSize = 16 * 16
|
||||
|
||||
var (
|
||||
rander = rand.Reader // random function
|
||||
poolEnabled = false
|
||||
poolMu sync.Mutex
|
||||
poolPos = randPoolSize // protected with poolMu
|
||||
pool [randPoolSize]byte // protected with poolMu
|
||||
)
|
||||
|
||||
type invalidLengthError struct{ len int }
|
||||
|
||||
func (err invalidLengthError) Error() string {
|
||||
return fmt.Sprintf("invalid UUID length: %d", err.len)
|
||||
}
|
||||
|
||||
// IsInvalidLengthError is matcher function for custom error invalidLengthError
|
||||
func IsInvalidLengthError(err error) bool {
|
||||
_, ok := err.(invalidLengthError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Parse decodes s into a UUID or returns an error. Both the standard UUID
|
||||
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
@@ -68,7 +89,7 @@ func Parse(s string) (UUID, error) {
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
|
||||
return uuid, invalidLengthError{len(s)}
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
@@ -112,7 +133,7 @@ func ParseBytes(b []byte) (UUID, error) {
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
|
||||
return uuid, invalidLengthError{len(b)}
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
@@ -243,3 +264,31 @@ func SetRand(r io.Reader) {
|
||||
}
|
||||
rander = r
|
||||
}
|
||||
|
||||
// EnableRandPool enables internal randomness pool used for Random
|
||||
// (Version 4) UUID generation. The pool contains random bytes read from
|
||||
// the random number generator on demand in batches. Enabling the pool
|
||||
// may improve the UUID generation throughput significantly.
|
||||
//
|
||||
// Since the pool is stored on the Go heap, this feature may be a bad fit
|
||||
// for security sensitive applications.
|
||||
//
|
||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||
// only be called when there is no possibility that New or any other
|
||||
// UUID Version 4 generation function will be called concurrently.
|
||||
func EnableRandPool() {
|
||||
poolEnabled = true
|
||||
}
|
||||
|
||||
// DisableRandPool disables the randomness pool if it was previously
|
||||
// enabled with EnableRandPool.
|
||||
//
|
||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||
// only be called when there is no possibility that New or any other
|
||||
// UUID Version 4 generation function will be called concurrently.
|
||||
func DisableRandPool() {
|
||||
poolEnabled = false
|
||||
defer poolMu.Unlock()
|
||||
poolMu.Lock()
|
||||
poolPos = randPoolSize
|
||||
}
|
||||
|
35
vendor/github.com/google/uuid/version4.go
generated
vendored
35
vendor/github.com/google/uuid/version4.go
generated
vendored
@@ -14,11 +14,21 @@ func New() UUID {
|
||||
return Must(NewRandom())
|
||||
}
|
||||
|
||||
// NewString creates a new random UUID and returns it as a string or panics.
|
||||
// NewString is equivalent to the expression
|
||||
//
|
||||
// uuid.New().String()
|
||||
func NewString() string {
|
||||
return Must(NewRandom()).String()
|
||||
}
|
||||
|
||||
// NewRandom returns a Random (Version 4) UUID.
|
||||
//
|
||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||
// package.
|
||||
//
|
||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||
//
|
||||
// A note about uniqueness derived from the UUID Wikipedia entry:
|
||||
//
|
||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||
@@ -27,7 +37,10 @@ func New() UUID {
|
||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||
// year and having one duplicate.
|
||||
func NewRandom() (UUID, error) {
|
||||
return NewRandomFromReader(rander)
|
||||
if !poolEnabled {
|
||||
return NewRandomFromReader(rander)
|
||||
}
|
||||
return newRandomFromPool()
|
||||
}
|
||||
|
||||
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
|
||||
@@ -41,3 +54,23 @@ func NewRandomFromReader(r io.Reader) (UUID, error) {
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
func newRandomFromPool() (UUID, error) {
|
||||
var uuid UUID
|
||||
poolMu.Lock()
|
||||
if poolPos == randPoolSize {
|
||||
_, err := io.ReadFull(rander, pool[:])
|
||||
if err != nil {
|
||||
poolMu.Unlock()
|
||||
return Nil, err
|
||||
}
|
||||
poolPos = 0
|
||||
}
|
||||
copy(uuid[:], pool[poolPos:(poolPos+16)])
|
||||
poolPos += 16
|
||||
poolMu.Unlock()
|
||||
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
||||
|
25
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
Normal file
25
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
|
||||
.idea/
|
||||
*.iml
|
9
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal file
9
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# This is the official list of Gorilla WebSocket authors for copyright
|
||||
# purposes.
|
||||
#
|
||||
# Please keep the list sorted.
|
||||
|
||||
Gary Burd <gary@beagledreams.com>
|
||||
Google LLC (https://opensource.google.com/)
|
||||
Joachim Bauch <mail@joachim-bauch.de>
|
||||
|
22
vendor/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal file
22
vendor/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
64
vendor/github.com/gorilla/websocket/README.md
generated
vendored
Normal file
64
vendor/github.com/gorilla/websocket/README.md
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
# Gorilla WebSocket
|
||||
|
||||
[](https://godoc.org/github.com/gorilla/websocket)
|
||||
[](https://circleci.com/gh/gorilla/websocket)
|
||||
|
||||
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
||||
|
||||
### Documentation
|
||||
|
||||
* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc)
|
||||
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
|
||||
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
|
||||
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
|
||||
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
|
||||
|
||||
### Status
|
||||
|
||||
The Gorilla WebSocket package provides a complete and tested implementation of
|
||||
the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The
|
||||
package API is stable.
|
||||
|
||||
### Installation
|
||||
|
||||
go get github.com/gorilla/websocket
|
||||
|
||||
### Protocol Compliance
|
||||
|
||||
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
|
||||
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
|
||||
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
|
||||
|
||||
### Gorilla WebSocket compared with other packages
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th>
|
||||
<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
|
||||
<tr><td>Passes <a href="https://github.com/crossbario/autobahn-testsuite">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
|
||||
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
|
||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
|
||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
|
||||
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
|
||||
<tr><td colspan="3">Other Features</tr></td>
|
||||
<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
|
||||
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
|
||||
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
|
||||
</table>
|
||||
|
||||
Notes:
|
||||
|
||||
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
|
||||
2. The application can get the type of a received data message by implementing
|
||||
a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal)
|
||||
function.
|
||||
3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries.
|
||||
Read returns when the input buffer is full or a frame boundary is
|
||||
encountered. Each call to Write sends a single frame message. The Gorilla
|
||||
io.Reader and io.WriteCloser operate on a single WebSocket message.
|
||||
|
395
vendor/github.com/gorilla/websocket/client.go
generated
vendored
Normal file
395
vendor/github.com/gorilla/websocket/client.go
generated
vendored
Normal file
@@ -0,0 +1,395 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrBadHandshake is returned when the server response to opening handshake is
|
||||
// invalid.
|
||||
var ErrBadHandshake = errors.New("websocket: bad handshake")
|
||||
|
||||
var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
|
||||
|
||||
// NewClient creates a new client connection using the given net connection.
|
||||
// The URL u specifies the host and request URI. Use requestHeader to specify
|
||||
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
|
||||
// (Cookie). Use the response.Header to get the selected subprotocol
|
||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||
//
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||
// etc.
|
||||
//
|
||||
// Deprecated: Use Dialer instead.
|
||||
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
|
||||
d := Dialer{
|
||||
ReadBufferSize: readBufSize,
|
||||
WriteBufferSize: writeBufSize,
|
||||
NetDial: func(net, addr string) (net.Conn, error) {
|
||||
return netConn, nil
|
||||
},
|
||||
}
|
||||
return d.Dial(u.String(), requestHeader)
|
||||
}
|
||||
|
||||
// A Dialer contains options for connecting to WebSocket server.
|
||||
type Dialer struct {
|
||||
// NetDial specifies the dial function for creating TCP connections. If
|
||||
// NetDial is nil, net.Dial is used.
|
||||
NetDial func(network, addr string) (net.Conn, error)
|
||||
|
||||
// NetDialContext specifies the dial function for creating TCP connections. If
|
||||
// NetDialContext is nil, net.DialContext is used.
|
||||
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// Proxy specifies a function to return a proxy for a given
|
||||
// Request. If the function returns a non-nil error, the
|
||||
// request is aborted with the provided error.
|
||||
// If Proxy is nil or returns a nil *URL, no proxy is used.
|
||||
Proxy func(*http.Request) (*url.URL, error)
|
||||
|
||||
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
|
||||
// If nil, the default configuration is used.
|
||||
TLSClientConfig *tls.Config
|
||||
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
|
||||
// size is zero, then a useful default size is used. The I/O buffer sizes
|
||||
// do not limit the size of the messages that can be sent or received.
|
||||
ReadBufferSize, WriteBufferSize int
|
||||
|
||||
// WriteBufferPool is a pool of buffers for write operations. If the value
|
||||
// is not set, then write buffers are allocated to the connection for the
|
||||
// lifetime of the connection.
|
||||
//
|
||||
// A pool is most useful when the application has a modest volume of writes
|
||||
// across a large number of connections.
|
||||
//
|
||||
// Applications should use a single pool for each unique value of
|
||||
// WriteBufferSize.
|
||||
WriteBufferPool BufferPool
|
||||
|
||||
// Subprotocols specifies the client's requested subprotocols.
|
||||
Subprotocols []string
|
||||
|
||||
// EnableCompression specifies if the client should attempt to negotiate
|
||||
// per message compression (RFC 7692). Setting this value to true does not
|
||||
// guarantee that compression will be supported. Currently only "no context
|
||||
// takeover" modes are supported.
|
||||
EnableCompression bool
|
||||
|
||||
// Jar specifies the cookie jar.
|
||||
// If Jar is nil, cookies are not sent in requests and ignored
|
||||
// in responses.
|
||||
Jar http.CookieJar
|
||||
}
|
||||
|
||||
// Dial creates a new client connection by calling DialContext with a background context.
|
||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
return d.DialContext(context.Background(), urlStr, requestHeader)
|
||||
}
|
||||
|
||||
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||
|
||||
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||
hostPort = u.Host
|
||||
hostNoPort = u.Host
|
||||
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
|
||||
hostNoPort = hostNoPort[:i]
|
||||
} else {
|
||||
switch u.Scheme {
|
||||
case "wss":
|
||||
hostPort += ":443"
|
||||
case "https":
|
||||
hostPort += ":443"
|
||||
default:
|
||||
hostPort += ":80"
|
||||
}
|
||||
}
|
||||
return hostPort, hostNoPort
|
||||
}
|
||||
|
||||
// DefaultDialer is a dialer with all fields set to the default values.
|
||||
var DefaultDialer = &Dialer{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
HandshakeTimeout: 45 * time.Second,
|
||||
}
|
||||
|
||||
// nilDialer is dialer to use when receiver is nil.
|
||||
var nilDialer = *DefaultDialer
|
||||
|
||||
// DialContext creates a new client connection. Use requestHeader to specify the
|
||||
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
||||
// Use the response.Header to get the selected subprotocol
|
||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||
//
|
||||
// The context will be used in the request and in the Dialer.
|
||||
//
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||
// etcetera. The response body may not contain the entire response and does not
|
||||
// need to be closed by the application.
|
||||
func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
if d == nil {
|
||||
d = &nilDialer
|
||||
}
|
||||
|
||||
challengeKey, err := generateChallengeKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "ws":
|
||||
u.Scheme = "http"
|
||||
case "wss":
|
||||
u.Scheme = "https"
|
||||
default:
|
||||
return nil, nil, errMalformedURL
|
||||
}
|
||||
|
||||
if u.User != nil {
|
||||
// User name and password are not allowed in websocket URIs.
|
||||
return nil, nil, errMalformedURL
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Method: "GET",
|
||||
URL: u,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: make(http.Header),
|
||||
Host: u.Host,
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// Set the cookies present in the cookie jar of the dialer
|
||||
if d.Jar != nil {
|
||||
for _, cookie := range d.Jar.Cookies(u) {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the request headers using the capitalization for names and values in
|
||||
// RFC examples. Although the capitalization shouldn't matter, there are
|
||||
// servers that depend on it. The Header.Set method is not used because the
|
||||
// method canonicalizes the header names.
|
||||
req.Header["Upgrade"] = []string{"websocket"}
|
||||
req.Header["Connection"] = []string{"Upgrade"}
|
||||
req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
|
||||
req.Header["Sec-WebSocket-Version"] = []string{"13"}
|
||||
if len(d.Subprotocols) > 0 {
|
||||
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
|
||||
}
|
||||
for k, vs := range requestHeader {
|
||||
switch {
|
||||
case k == "Host":
|
||||
if len(vs) > 0 {
|
||||
req.Host = vs[0]
|
||||
}
|
||||
case k == "Upgrade" ||
|
||||
k == "Connection" ||
|
||||
k == "Sec-Websocket-Key" ||
|
||||
k == "Sec-Websocket-Version" ||
|
||||
k == "Sec-Websocket-Extensions" ||
|
||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||
case k == "Sec-Websocket-Protocol":
|
||||
req.Header["Sec-WebSocket-Protocol"] = vs
|
||||
default:
|
||||
req.Header[k] = vs
|
||||
}
|
||||
}
|
||||
|
||||
if d.EnableCompression {
|
||||
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
|
||||
}
|
||||
|
||||
if d.HandshakeTimeout != 0 {
|
||||
var cancel func()
|
||||
ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
// Get network dial function.
|
||||
var netDial func(network, add string) (net.Conn, error)
|
||||
|
||||
if d.NetDialContext != nil {
|
||||
netDial = func(network, addr string) (net.Conn, error) {
|
||||
return d.NetDialContext(ctx, network, addr)
|
||||
}
|
||||
} else if d.NetDial != nil {
|
||||
netDial = d.NetDial
|
||||
} else {
|
||||
netDialer := &net.Dialer{}
|
||||
netDial = func(network, addr string) (net.Conn, error) {
|
||||
return netDialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// If needed, wrap the dial function to set the connection deadline.
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
forwardDial := netDial
|
||||
netDial = func(network, addr string) (net.Conn, error) {
|
||||
c, err := forwardDial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.SetDeadline(deadline)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
// If needed, wrap the dial function to connect through a proxy.
|
||||
if d.Proxy != nil {
|
||||
proxyURL, err := d.Proxy(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if proxyURL != nil {
|
||||
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
netDial = dialer.Dial
|
||||
}
|
||||
}
|
||||
|
||||
hostPort, hostNoPort := hostPortNoPort(u)
|
||||
trace := httptrace.ContextClientTrace(ctx)
|
||||
if trace != nil && trace.GetConn != nil {
|
||||
trace.GetConn(hostPort)
|
||||
}
|
||||
|
||||
netConn, err := netDial("tcp", hostPort)
|
||||
if trace != nil && trace.GotConn != nil {
|
||||
trace.GotConn(httptrace.GotConnInfo{
|
||||
Conn: netConn,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if netConn != nil {
|
||||
netConn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if u.Scheme == "https" {
|
||||
cfg := cloneTLSConfig(d.TLSClientConfig)
|
||||
if cfg.ServerName == "" {
|
||||
cfg.ServerName = hostNoPort
|
||||
}
|
||||
tlsConn := tls.Client(netConn, cfg)
|
||||
netConn = tlsConn
|
||||
|
||||
var err error
|
||||
if trace != nil {
|
||||
err = doHandshakeWithTrace(trace, tlsConn, cfg)
|
||||
} else {
|
||||
err = doHandshake(tlsConn, cfg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil)
|
||||
|
||||
if err := req.Write(netConn); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if trace != nil && trace.GotFirstResponseByte != nil {
|
||||
if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 {
|
||||
trace.GotFirstResponseByte()
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(conn.br, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if d.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
d.Jar.SetCookies(u, rc)
|
||||
}
|
||||
}
|
||||
|
||||
if resp.StatusCode != 101 ||
|
||||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
|
||||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
|
||||
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
|
||||
// Before closing the network connection on return from this
|
||||
// function, slurp up some of the response to aid application
|
||||
// debugging.
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := io.ReadFull(resp.Body, buf)
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
|
||||
return nil, resp, ErrBadHandshake
|
||||
}
|
||||
|
||||
for _, ext := range parseExtensions(resp.Header) {
|
||||
if ext[""] != "permessage-deflate" {
|
||||
continue
|
||||
}
|
||||
_, snct := ext["server_no_context_takeover"]
|
||||
_, cnct := ext["client_no_context_takeover"]
|
||||
if !snct || !cnct {
|
||||
return nil, resp, errInvalidCompression
|
||||
}
|
||||
conn.newCompressionWriter = compressNoContextTakeover
|
||||
conn.newDecompressionReader = decompressNoContextTakeover
|
||||
break
|
||||
}
|
||||
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
||||
|
||||
netConn.SetDeadline(time.Time{})
|
||||
netConn = nil // to avoid close in defer.
|
||||
return conn, resp, nil
|
||||
}
|
||||
|
||||
func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !cfg.InsecureSkipVerify {
|
||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
16
vendor/github.com/gorilla/websocket/client_clone.go
generated
vendored
Normal file
16
vendor/github.com/gorilla/websocket/client_clone.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||
if cfg == nil {
|
||||
return &tls.Config{}
|
||||
}
|
||||
return cfg.Clone()
|
||||
}
|
38
vendor/github.com/gorilla/websocket/client_clone_legacy.go
generated
vendored
Normal file
38
vendor/github.com/gorilla/websocket/client_clone_legacy.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// cloneTLSConfig clones all public fields except the fields
|
||||
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
|
||||
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
|
||||
// config in active use.
|
||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||
if cfg == nil {
|
||||
return &tls.Config{}
|
||||
}
|
||||
return &tls.Config{
|
||||
Rand: cfg.Rand,
|
||||
Time: cfg.Time,
|
||||
Certificates: cfg.Certificates,
|
||||
NameToCertificate: cfg.NameToCertificate,
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
RootCAs: cfg.RootCAs,
|
||||
NextProtos: cfg.NextProtos,
|
||||
ServerName: cfg.ServerName,
|
||||
ClientAuth: cfg.ClientAuth,
|
||||
ClientCAs: cfg.ClientCAs,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
CipherSuites: cfg.CipherSuites,
|
||||
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
||||
ClientSessionCache: cfg.ClientSessionCache,
|
||||
MinVersion: cfg.MinVersion,
|
||||
MaxVersion: cfg.MaxVersion,
|
||||
CurvePreferences: cfg.CurvePreferences,
|
||||
}
|
||||
}
|
148
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
Normal file
148
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6
|
||||
maxCompressionLevel = flate.BestCompression
|
||||
defaultCompressionLevel = 1
|
||||
)
|
||||
|
||||
var (
|
||||
flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool
|
||||
flateReaderPool = sync.Pool{New: func() interface{} {
|
||||
return flate.NewReader(nil)
|
||||
}}
|
||||
)
|
||||
|
||||
func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
|
||||
const tail =
|
||||
// Add four bytes as specified in RFC
|
||||
"\x00\x00\xff\xff" +
|
||||
// Add final block to squelch unexpected EOF error from flate reader.
|
||||
"\x01\x00\x00\xff\xff"
|
||||
|
||||
fr, _ := flateReaderPool.Get().(io.ReadCloser)
|
||||
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
|
||||
return &flateReadWrapper{fr}
|
||||
}
|
||||
|
||||
func isValidCompressionLevel(level int) bool {
|
||||
return minCompressionLevel <= level && level <= maxCompressionLevel
|
||||
}
|
||||
|
||||
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
|
||||
p := &flateWriterPools[level-minCompressionLevel]
|
||||
tw := &truncWriter{w: w}
|
||||
fw, _ := p.Get().(*flate.Writer)
|
||||
if fw == nil {
|
||||
fw, _ = flate.NewWriter(tw, level)
|
||||
} else {
|
||||
fw.Reset(tw)
|
||||
}
|
||||
return &flateWriteWrapper{fw: fw, tw: tw, p: p}
|
||||
}
|
||||
|
||||
// truncWriter is an io.Writer that writes all but the last four bytes of the
|
||||
// stream to another io.Writer.
|
||||
type truncWriter struct {
|
||||
w io.WriteCloser
|
||||
n int
|
||||
p [4]byte
|
||||
}
|
||||
|
||||
func (w *truncWriter) Write(p []byte) (int, error) {
|
||||
n := 0
|
||||
|
||||
// fill buffer first for simplicity.
|
||||
if w.n < len(w.p) {
|
||||
n = copy(w.p[w.n:], p)
|
||||
p = p[n:]
|
||||
w.n += n
|
||||
if len(p) == 0 {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
|
||||
m := len(p)
|
||||
if m > len(w.p) {
|
||||
m = len(w.p)
|
||||
}
|
||||
|
||||
if nn, err := w.w.Write(w.p[:m]); err != nil {
|
||||
return n + nn, err
|
||||
}
|
||||
|
||||
copy(w.p[:], w.p[m:])
|
||||
copy(w.p[len(w.p)-m:], p[len(p)-m:])
|
||||
nn, err := w.w.Write(p[:len(p)-m])
|
||||
return n + nn, err
|
||||
}
|
||||
|
||||
type flateWriteWrapper struct {
|
||||
fw *flate.Writer
|
||||
tw *truncWriter
|
||||
p *sync.Pool
|
||||
}
|
||||
|
||||
func (w *flateWriteWrapper) Write(p []byte) (int, error) {
|
||||
if w.fw == nil {
|
||||
return 0, errWriteClosed
|
||||
}
|
||||
return w.fw.Write(p)
|
||||
}
|
||||
|
||||
func (w *flateWriteWrapper) Close() error {
|
||||
if w.fw == nil {
|
||||
return errWriteClosed
|
||||
}
|
||||
err1 := w.fw.Flush()
|
||||
w.p.Put(w.fw)
|
||||
w.fw = nil
|
||||
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
|
||||
return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
|
||||
}
|
||||
err2 := w.tw.w.Close()
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
type flateReadWrapper struct {
|
||||
fr io.ReadCloser
|
||||
}
|
||||
|
||||
func (r *flateReadWrapper) Read(p []byte) (int, error) {
|
||||
if r.fr == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
n, err := r.fr.Read(p)
|
||||
if err == io.EOF {
|
||||
// Preemptively place the reader back in the pool. This helps with
|
||||
// scenarios where the application does not call NextReader() soon after
|
||||
// this final read.
|
||||
r.Close()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *flateReadWrapper) Close() error {
|
||||
if r.fr == nil {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
err := r.fr.Close()
|
||||
flateReaderPool.Put(r.fr)
|
||||
r.fr = nil
|
||||
return err
|
||||
}
|
1201
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
Normal file
1201
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
15
vendor/github.com/gorilla/websocket/conn_write.go
generated
vendored
Normal file
15
vendor/github.com/gorilla/websocket/conn_write.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import "net"
|
||||
|
||||
func (c *Conn) writeBufs(bufs ...[]byte) error {
|
||||
b := net.Buffers(bufs)
|
||||
_, err := b.WriteTo(c.conn)
|
||||
return err
|
||||
}
|
18
vendor/github.com/gorilla/websocket/conn_write_legacy.go
generated
vendored
Normal file
18
vendor/github.com/gorilla/websocket/conn_write_legacy.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
func (c *Conn) writeBufs(bufs ...[]byte) error {
|
||||
for _, buf := range bufs {
|
||||
if len(buf) > 0 {
|
||||
if _, err := c.conn.Write(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
227
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
Normal file
227
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package websocket implements the WebSocket protocol defined in RFC 6455.
|
||||
//
|
||||
// Overview
|
||||
//
|
||||
// The Conn type represents a WebSocket connection. A server application calls
|
||||
// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn:
|
||||
//
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// ReadBufferSize: 1024,
|
||||
// WriteBufferSize: 1024,
|
||||
// }
|
||||
//
|
||||
// func handler(w http.ResponseWriter, r *http.Request) {
|
||||
// conn, err := upgrader.Upgrade(w, r, nil)
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// return
|
||||
// }
|
||||
// ... Use conn to send and receive messages.
|
||||
// }
|
||||
//
|
||||
// Call the connection's WriteMessage and ReadMessage methods to send and
|
||||
// receive messages as a slice of bytes. This snippet of code shows how to echo
|
||||
// messages using these methods:
|
||||
//
|
||||
// for {
|
||||
// messageType, p, err := conn.ReadMessage()
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// return
|
||||
// }
|
||||
// if err := conn.WriteMessage(messageType, p); err != nil {
|
||||
// log.Println(err)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// In above snippet of code, p is a []byte and messageType is an int with value
|
||||
// websocket.BinaryMessage or websocket.TextMessage.
|
||||
//
|
||||
// An application can also send and receive messages using the io.WriteCloser
|
||||
// and io.Reader interfaces. To send a message, call the connection NextWriter
|
||||
// method to get an io.WriteCloser, write the message to the writer and close
|
||||
// the writer when done. To receive a message, call the connection NextReader
|
||||
// method to get an io.Reader and read until io.EOF is returned. This snippet
|
||||
// shows how to echo messages using the NextWriter and NextReader methods:
|
||||
//
|
||||
// for {
|
||||
// messageType, r, err := conn.NextReader()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// w, err := conn.NextWriter(messageType)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if _, err := io.Copy(w, r); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if err := w.Close(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Data Messages
|
||||
//
|
||||
// The WebSocket protocol distinguishes between text and binary data messages.
|
||||
// Text messages are interpreted as UTF-8 encoded text. The interpretation of
|
||||
// binary messages is left to the application.
|
||||
//
|
||||
// This package uses the TextMessage and BinaryMessage integer constants to
|
||||
// identify the two data message types. The ReadMessage and NextReader methods
|
||||
// return the type of the received message. The messageType argument to the
|
||||
// WriteMessage and NextWriter methods specifies the type of a sent message.
|
||||
//
|
||||
// It is the application's responsibility to ensure that text messages are
|
||||
// valid UTF-8 encoded text.
|
||||
//
|
||||
// Control Messages
|
||||
//
|
||||
// The WebSocket protocol defines three types of control messages: close, ping
|
||||
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
||||
// methods to send a control message to the peer.
|
||||
//
|
||||
// Connections handle received close messages by calling the handler function
|
||||
// set with the SetCloseHandler method and by returning a *CloseError from the
|
||||
// NextReader, ReadMessage or the message Read method. The default close
|
||||
// handler sends a close message to the peer.
|
||||
//
|
||||
// Connections handle received ping messages by calling the handler function
|
||||
// set with the SetPingHandler method. The default ping handler sends a pong
|
||||
// message to the peer.
|
||||
//
|
||||
// Connections handle received pong messages by calling the handler function
|
||||
// set with the SetPongHandler method. The default pong handler does nothing.
|
||||
// If an application sends ping messages, then the application should set a
|
||||
// pong handler to receive the corresponding pong.
|
||||
//
|
||||
// The control message handler functions are called from the NextReader,
|
||||
// ReadMessage and message reader Read methods. The default close and ping
|
||||
// handlers can block these methods for a short time when the handler writes to
|
||||
// the connection.
|
||||
//
|
||||
// The application must read the connection to process close, ping and pong
|
||||
// messages sent from the peer. If the application is not otherwise interested
|
||||
// in messages from the peer, then the application should start a goroutine to
|
||||
// read and discard messages from the peer. A simple example is:
|
||||
//
|
||||
// func readLoop(c *websocket.Conn) {
|
||||
// for {
|
||||
// if _, _, err := c.NextReader(); err != nil {
|
||||
// c.Close()
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Concurrency
|
||||
//
|
||||
// Connections support one concurrent reader and one concurrent writer.
|
||||
//
|
||||
// Applications are responsible for ensuring that no more than one goroutine
|
||||
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
|
||||
// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
|
||||
// that no more than one goroutine calls the read methods (NextReader,
|
||||
// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
|
||||
// concurrently.
|
||||
//
|
||||
// The Close and WriteControl methods can be called concurrently with all other
|
||||
// methods.
|
||||
//
|
||||
// Origin Considerations
|
||||
//
|
||||
// Web browsers allow Javascript applications to open a WebSocket connection to
|
||||
// any host. It's up to the server to enforce an origin policy using the Origin
|
||||
// request header sent by the browser.
|
||||
//
|
||||
// The Upgrader calls the function specified in the CheckOrigin field to check
|
||||
// the origin. If the CheckOrigin function returns false, then the Upgrade
|
||||
// method fails the WebSocket handshake with HTTP status 403.
|
||||
//
|
||||
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
|
||||
// the handshake if the Origin request header is present and the Origin host is
|
||||
// not equal to the Host request header.
|
||||
//
|
||||
// The deprecated package-level Upgrade function does not perform origin
|
||||
// checking. The application is responsible for checking the Origin header
|
||||
// before calling the Upgrade function.
|
||||
//
|
||||
// Buffers
|
||||
//
|
||||
// Connections buffer network input and output to reduce the number
|
||||
// of system calls when reading or writing messages.
|
||||
//
|
||||
// Write buffers are also used for constructing WebSocket frames. See RFC 6455,
|
||||
// Section 5 for a discussion of message framing. A WebSocket frame header is
|
||||
// written to the network each time a write buffer is flushed to the network.
|
||||
// Decreasing the size of the write buffer can increase the amount of framing
|
||||
// overhead on the connection.
|
||||
//
|
||||
// The buffer sizes in bytes are specified by the ReadBufferSize and
|
||||
// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default
|
||||
// size of 4096 when a buffer size field is set to zero. The Upgrader reuses
|
||||
// buffers created by the HTTP server when a buffer size field is set to zero.
|
||||
// The HTTP server buffers have a size of 4096 at the time of this writing.
|
||||
//
|
||||
// The buffer sizes do not limit the size of a message that can be read or
|
||||
// written by a connection.
|
||||
//
|
||||
// Buffers are held for the lifetime of the connection by default. If the
|
||||
// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the
|
||||
// write buffer only when writing a message.
|
||||
//
|
||||
// Applications should tune the buffer sizes to balance memory use and
|
||||
// performance. Increasing the buffer size uses more memory, but can reduce the
|
||||
// number of system calls to read or write the network. In the case of writing,
|
||||
// increasing the buffer size can reduce the number of frame headers written to
|
||||
// the network.
|
||||
//
|
||||
// Some guidelines for setting buffer parameters are:
|
||||
//
|
||||
// Limit the buffer sizes to the maximum expected message size. Buffers larger
|
||||
// than the largest message do not provide any benefit.
|
||||
//
|
||||
// Depending on the distribution of message sizes, setting the buffer size to
|
||||
// a value less than the maximum expected message size can greatly reduce memory
|
||||
// use with a small impact on performance. Here's an example: If 99% of the
|
||||
// messages are smaller than 256 bytes and the maximum message size is 512
|
||||
// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls
|
||||
// than a buffer size of 512 bytes. The memory savings is 50%.
|
||||
//
|
||||
// A write buffer pool is useful when the application has a modest number
|
||||
// writes over a large number of connections. when buffers are pooled, a larger
|
||||
// buffer size has a reduced impact on total memory use and has the benefit of
|
||||
// reducing system calls and frame overhead.
|
||||
//
|
||||
// Compression EXPERIMENTAL
|
||||
//
|
||||
// Per message compression extensions (RFC 7692) are experimentally supported
|
||||
// by this package in a limited capacity. Setting the EnableCompression option
|
||||
// to true in Dialer or Upgrader will attempt to negotiate per message deflate
|
||||
// support.
|
||||
//
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// EnableCompression: true,
|
||||
// }
|
||||
//
|
||||
// If compression was successfully negotiated with the connection's peer, any
|
||||
// message received in compressed form will be automatically decompressed.
|
||||
// All Read methods will return uncompressed bytes.
|
||||
//
|
||||
// Per message compression of messages written to a connection can be enabled
|
||||
// or disabled by calling the corresponding Conn method:
|
||||
//
|
||||
// conn.EnableWriteCompression(false)
|
||||
//
|
||||
// Currently this package does not support compression with "context takeover".
|
||||
// This means that messages must be compressed and decompressed in isolation,
|
||||
// without retaining sliding window or dictionary state across messages. For
|
||||
// more details refer to RFC 7692.
|
||||
//
|
||||
// Use of compression is experimental and may result in decreased performance.
|
||||
package websocket
|
3
vendor/github.com/gorilla/websocket/go.mod
generated
vendored
Normal file
3
vendor/github.com/gorilla/websocket/go.mod
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/gorilla/websocket
|
||||
|
||||
go 1.12
|
0
vendor/github.com/gorilla/websocket/go.sum
generated
vendored
Normal file
0
vendor/github.com/gorilla/websocket/go.sum
generated
vendored
Normal file
42
vendor/github.com/gorilla/websocket/join.go
generated
vendored
Normal file
42
vendor/github.com/gorilla/websocket/join.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JoinMessages concatenates received messages to create a single io.Reader.
|
||||
// The string term is appended to each message. The returned reader does not
|
||||
// support concurrent calls to the Read method.
|
||||
func JoinMessages(c *Conn, term string) io.Reader {
|
||||
return &joinReader{c: c, term: term}
|
||||
}
|
||||
|
||||
type joinReader struct {
|
||||
c *Conn
|
||||
term string
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (r *joinReader) Read(p []byte) (int, error) {
|
||||
if r.r == nil {
|
||||
var err error
|
||||
_, r.r, err = r.c.NextReader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if r.term != "" {
|
||||
r.r = io.MultiReader(r.r, strings.NewReader(r.term))
|
||||
}
|
||||
}
|
||||
n, err := r.r.Read(p)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
r.r = nil
|
||||
}
|
||||
return n, err
|
||||
}
|
60
vendor/github.com/gorilla/websocket/json.go
generated
vendored
Normal file
60
vendor/github.com/gorilla/websocket/json.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
// WriteJSON writes the JSON encoding of v as a message.
|
||||
//
|
||||
// Deprecated: Use c.WriteJSON instead.
|
||||
func WriteJSON(c *Conn, v interface{}) error {
|
||||
return c.WriteJSON(v)
|
||||
}
|
||||
|
||||
// WriteJSON writes the JSON encoding of v as a message.
|
||||
//
|
||||
// See the documentation for encoding/json Marshal for details about the
|
||||
// conversion of Go values to JSON.
|
||||
func (c *Conn) WriteJSON(v interface{}) error {
|
||||
w, err := c.NextWriter(TextMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err1 := json.NewEncoder(w).Encode(v)
|
||||
err2 := w.Close()
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||
// it in the value pointed to by v.
|
||||
//
|
||||
// Deprecated: Use c.ReadJSON instead.
|
||||
func ReadJSON(c *Conn, v interface{}) error {
|
||||
return c.ReadJSON(v)
|
||||
}
|
||||
|
||||
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||
// it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for the encoding/json Unmarshal function for details
|
||||
// about the conversion of JSON to a Go value.
|
||||
func (c *Conn) ReadJSON(v interface{}) error {
|
||||
_, r, err := c.NextReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.NewDecoder(r).Decode(v)
|
||||
if err == io.EOF {
|
||||
// One value is expected in the message.
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return err
|
||||
}
|
54
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
Normal file
54
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package websocket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||
|
||||
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||
// Mask one byte at a time for small buffers.
|
||||
if len(b) < 2*wordSize {
|
||||
for i := range b {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
return pos & 3
|
||||
}
|
||||
|
||||
// Mask one byte at a time to word boundary.
|
||||
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
|
||||
n = wordSize - n
|
||||
for i := range b[:n] {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
b = b[n:]
|
||||
}
|
||||
|
||||
// Create aligned word size key.
|
||||
var k [wordSize]byte
|
||||
for i := range k {
|
||||
k[i] = key[(pos+i)&3]
|
||||
}
|
||||
kw := *(*uintptr)(unsafe.Pointer(&k))
|
||||
|
||||
// Mask one word at a time.
|
||||
n := (len(b) / wordSize) * wordSize
|
||||
for i := 0; i < n; i += wordSize {
|
||||
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
|
||||
}
|
||||
|
||||
// Mask one byte at a time for remaining bytes.
|
||||
b = b[n:]
|
||||
for i := range b {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
|
||||
return pos & 3
|
||||
}
|
15
vendor/github.com/gorilla/websocket/mask_safe.go
generated
vendored
Normal file
15
vendor/github.com/gorilla/websocket/mask_safe.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package websocket
|
||||
|
||||
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||
for i := range b {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
return pos & 3
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user