mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-22 15:38:06 +00:00
Compare commits
110 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0f1dcdd38a | ||
![]() |
4c04c6699b | ||
![]() |
cd03176aae | ||
![]() |
f194485a1b | ||
![]() |
d1254b06e7 | ||
![]() |
7c84f9d3a4 | ||
![]() |
13abcb9c8f | ||
![]() |
b717823fd0 | ||
![]() |
c9b969def1 | ||
![]() |
fd92ef86ab | ||
![]() |
842c8febdc | ||
![]() |
49a5e93c44 | ||
![]() |
ac646ec56c | ||
![]() |
458ed24ca3 | ||
![]() |
bb7b565457 | ||
![]() |
17d44d90bd | ||
![]() |
0903bacfbd | ||
![]() |
32360d8a83 | ||
![]() |
2373fbc87f | ||
![]() |
704116b855 | ||
![]() |
55b5781d7a | ||
![]() |
1aa27fdba0 | ||
![]() |
f164a93b72 | ||
![]() |
9d0736285f | ||
![]() |
aa1b4ba857 | ||
![]() |
ae9b673f96 | ||
![]() |
a0475d7170 | ||
![]() |
922f3c8929 | ||
![]() |
1fa5bccec2 | ||
![]() |
cff4d194ba | ||
![]() |
475860a364 | ||
![]() |
64aad3d573 | ||
![]() |
0582c2d6e1 | ||
![]() |
d1be03edd0 | ||
![]() |
83b294690a | ||
![]() |
ef7361b3c1 | ||
![]() |
04c1fc1236 | ||
![]() |
3723c962fe | ||
![]() |
4d2120061d | ||
![]() |
c95078c34c | ||
![]() |
4527336c37 | ||
![]() |
0b6b07b867 | ||
![]() |
4baad12e10 | ||
![]() |
efbe96e93a | ||
![]() |
ac06f8db13 | ||
![]() |
72511ed687 | ||
![]() |
4757263c66 | ||
![]() |
97e06e5c4d | ||
![]() |
a432b42814 | ||
![]() |
f88ea6696b | ||
![]() |
1498c223a5 | ||
![]() |
432f2c6cf6 | ||
![]() |
a49c6ee045 | ||
![]() |
8881782559 | ||
![]() |
dcb5d5675a | ||
![]() |
b567b99774 | ||
![]() |
02c90d424b | ||
![]() |
4428515407 | ||
![]() |
949a840779 | ||
![]() |
ced45c33d4 | ||
![]() |
4091f80351 | ||
![]() |
c94e7a5557 | ||
![]() |
3fbd0b32cd | ||
![]() |
2c55fbc5ed | ||
![]() |
fcb97e1482 | ||
![]() |
b3346cbc25 | ||
![]() |
8c0f1a7837 | ||
![]() |
eda5612827 | ||
![]() |
5f232b121a | ||
![]() |
f72e5243b0 | ||
![]() |
8fc852a4dd | ||
![]() |
e6998497a2 | ||
![]() |
4b36cd80bd | ||
![]() |
030d451c94 | ||
![]() |
1e73bc1220 | ||
![]() |
a42a96bd26 | ||
![]() |
c8fe537ad1 | ||
![]() |
9b4d8eb292 | ||
![]() |
91c3422597 | ||
![]() |
d3d0cfa281 | ||
![]() |
5c41962aea | ||
![]() |
4413e61f2a | ||
![]() |
63e3cd15fb | ||
![]() |
ffb9a4f22a | ||
![]() |
10cfb55350 | ||
![]() |
372a5f4aa9 | ||
![]() |
3bb2f0e9d3 | ||
![]() |
a78b197db8 | ||
![]() |
514ef95330 | ||
![]() |
55922b52b7 | ||
![]() |
0c0a498726 | ||
![]() |
3d05fcc0d7 | ||
![]() |
4c9801322b | ||
![]() |
26ff2408ba | ||
![]() |
2dbfc7ecdd | ||
![]() |
aaddfd0c79 | ||
![]() |
e72705e9fa | ||
![]() |
c2d5c835c1 | ||
![]() |
e4b945ed56 | ||
![]() |
50862a8321 | ||
![]() |
c7548af5c3 | ||
![]() |
d00fc40e90 | ||
![]() |
802e7c5b56 | ||
![]() |
63dcaac407 | ||
![]() |
fe930fef05 | ||
![]() |
702974f750 | ||
![]() |
ea8773bfee | ||
![]() |
a84b5337ea | ||
![]() |
cd1c978d18 | ||
![]() |
34b8f9ee3d |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -7,12 +7,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ^1.19
|
||||
go-version: ^1.21
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
13
.github/workflows/pr-check-signed-commits.yml
vendored
Normal file
13
.github/workflows/pr-check-signed-commits.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Check signed commits in PR
|
||||
on: pull_request_target
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Check signed commits in PR
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check signed commits in PR
|
||||
uses: 1Password/check-signed-commits-action@v1
|
20
.github/workflows/release-pr.yml
vendored
20
.github/workflows/release-pr.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
-
|
||||
id: is_release_branch_without_pr
|
||||
name: Find matching PR
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -43,19 +43,20 @@ jobs:
|
||||
name: Create Release Pull Request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Parse release version
|
||||
id: get_version
|
||||
run: echo "::set-output name=version::$(echo $GITHUB_REF | sed 's|^refs/heads/release/v?*||g')"
|
||||
run: echo "version=$(echo "$GITHUB_REF" | sed 's|^refs/heads/release/v?*||g')" >> $GITHUB_OUTPUT
|
||||
|
||||
- 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
|
||||
DELIMITER="$(openssl rand -hex 8)" # DELIMITER is randomly generated and unique for each run. For more information, see https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections.
|
||||
|
||||
PR_BODY_CONTENT="
|
||||
This is an automated PR for a new release.
|
||||
|
||||
Please check the following before approving:
|
||||
@@ -64,14 +65,9 @@ jobs:
|
||||
---
|
||||
## 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")"
|
||||
echo "pr_body<<${DELIMITER}${PR_BODY_CONTENT}${DELIMITER}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Create Pull Request via API
|
||||
id: post_pr
|
||||
|
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -12,13 +12,13 @@ jobs:
|
||||
DOCKER_CLI_EXPERIMENTAL: "enabled"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
1password/onepassword-operator
|
||||
@@ -33,19 +33,19 @@ jobs:
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker Login
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
|
48
CHANGELOG.md
48
CHANGELOG.md
@@ -12,6 +12,54 @@
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v1.9.0)
|
||||
# v1.9.0
|
||||
|
||||
## Features
|
||||
* Enable the Operator to authenticate to 1Password using service accounts. {#160}
|
||||
|
||||
## Fixes
|
||||
* Update Operator to use SDK v1.34.1. {#185}
|
||||
* Pass Kubernetes context down to SDK/Connect. {#199}
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v1.8.1)
|
||||
# v1.8.1
|
||||
|
||||
## Fixes
|
||||
* Upgrade operator to use Operator SDK v1.33.0. {#180}
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v1.8.0)
|
||||
# v1.8.0
|
||||
|
||||
## Features
|
||||
* Added volume projected detection. Credit to @mmorejon. {#168}
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v1.7.1)
|
||||
# v1.7.1
|
||||
|
||||
## Fixes
|
||||
* Adjusting logging level on various logs to reduce unnecessary logging. {#164}
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v1.7.0)
|
||||
# v1.7.0
|
||||
|
||||
## Features
|
||||
* Upgraded operator to version 1.29.0. {#162}
|
||||
* Upgraded Golang version to 1.20. {#161}
|
||||
* Upgraded 1Password Connect version to 1.5.1. {#161}
|
||||
* Added runAsNonRoot and allowPrivalegeEscalation to specs. {#151}
|
||||
* Added code quality improvements. {#146}
|
||||
|
||||
---
|
||||
|
||||
[//]: # (START/v1.6.0)
|
||||
# v1.6.0
|
||||
|
||||
|
72
CONTRIBUTING.md
Normal file
72
CONTRIBUTING.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Contributing
|
||||
|
||||
Thank you for your interest in contributing to the 1Password Kubernetes Operator project 👋! Before you start, please take a moment to read through this guide to understand our contribution process.
|
||||
|
||||
## Testing
|
||||
|
||||
- For functional testing, run the local version of the operator. From the project root:
|
||||
|
||||
```sh
|
||||
# Go to the K8s environment (e.g. minikube)
|
||||
eval $(minikube docker-env)
|
||||
|
||||
# Build the local Docker image for the operator
|
||||
make docker-build
|
||||
|
||||
# Deploy the operator
|
||||
make deploy
|
||||
|
||||
# Remove the operator from K8s
|
||||
make undeploy
|
||||
```
|
||||
|
||||
- For testing the changes made to the `OnePasswordItem` Custom Resource Definition (CRD), you need to re-generate the object:
|
||||
```sh
|
||||
make manifests
|
||||
```
|
||||
|
||||
- Run tests for the operator:
|
||||
|
||||
```sh
|
||||
make test
|
||||
```
|
||||
|
||||
You can check other available commands that may come in handy by running `make help`.
|
||||
|
||||
## Debugging
|
||||
|
||||
- Running `kubectl describe pod` will fetch details about pods. This includes configuration information about the container(s) and Pod (labels, resource requirements, etc) and status information about the container(s) and Pod (state, readiness, restart count, events, etc.).
|
||||
- Running `kubectl logs ${POD_NAME} ${CONTAINER_NAME}` will print the logs from the container(s) in a pod. This can help with debugging issues by inspection.
|
||||
- Running `kubectl exec ${POD_NAME} -c ${CONTAINER_NAME} -- ${CMD}` allows executing a command inside a specific container.
|
||||
|
||||
For more debugging documentation, see: https://kubernetes.io/docs/tasks/debug/debug-application/debug-pods/
|
||||
|
||||
## Documentation Updates
|
||||
|
||||
If applicable, update the [USAGEGUIDE.md](./USAGEGUIDE.md) and [README.md](./README.md) to reflect any changes introduced by the new code.
|
||||
|
||||
## Sign your commits
|
||||
|
||||
To get your PR merged, we require you to sign your commits. There are three options you can choose from.
|
||||
|
||||
### Sign commits with 1Password
|
||||
|
||||
You can sign commits using 1Password, which lets you sign commits with biometrics without the signing key leaving the local 1Password process.
|
||||
|
||||
Learn how to use [1Password to sign your commits](https://developer.1password.com/docs/ssh/git-commit-signing/).
|
||||
|
||||
### Sign commits with ssh-agent
|
||||
|
||||
Follow the steps below to set up commit signing with `ssh-agent`:
|
||||
|
||||
1. [Generate an SSH key and add it to ssh-agent](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent)
|
||||
2. [Add the SSH key to your GitHub account](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account)
|
||||
3. [Configure git to use your SSH key for commits signing](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key#telling-git-about-your-ssh-key)
|
||||
|
||||
### Sign commits with gpg
|
||||
|
||||
Follow the steps below to set up commit signing with `gpg`:
|
||||
|
||||
1. [Generate a GPG key](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key)
|
||||
2. [Add the GPG key to your GitHub account](https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account)
|
||||
3. [Configure git to use your GPG key for commits signing](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key#telling-git-about-your-gpg-key)
|
14
Dockerfile
14
Dockerfile
@@ -1,5 +1,5 @@
|
||||
# Build the manager binary
|
||||
FROM golang:1.19 as builder
|
||||
FROM golang:1.24 as builder
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -7,17 +7,16 @@ WORKDIR /workspace
|
||||
# Copy the Go Modules manifests
|
||||
COPY go.mod go.mod
|
||||
COPY go.sum go.sum
|
||||
# cache deps before building and copying source so that we don't need to re-download as much
|
||||
# and so that source changes don't invalidate our downloaded layer
|
||||
|
||||
# Download dependencies
|
||||
RUN go mod download
|
||||
|
||||
# Copy the go source
|
||||
COPY main.go main.go
|
||||
COPY cmd/main.go cmd/main.go
|
||||
COPY api/ api/
|
||||
COPY controllers/ controllers/
|
||||
COPY internal/controller/ internal/controller/
|
||||
COPY pkg/ pkg/
|
||||
COPY version/ version/
|
||||
COPY vendor/ vendor/
|
||||
|
||||
# Build
|
||||
# the GOARCH has not a default value to allow the binary be built according to the host where the command
|
||||
@@ -28,8 +27,7 @@ RUN CGO_ENABLED=0 \
|
||||
GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \
|
||||
go build \
|
||||
-ldflags "-X \"github.com/1Password/onepassword-operator/version.Version=$operator_version\"" \
|
||||
-mod vendor \
|
||||
-a -o manager main.go
|
||||
-a -o manager cmd/main.go
|
||||
|
||||
# Use distroless as minimal base image to package the manager binary
|
||||
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||
|
93
Makefile
93
Makefile
@@ -5,7 +5,7 @@ export MAIN_BRANCH ?= main
|
||||
# To re-generate a bundle for another specific version without changing the standard setup, you can:
|
||||
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
|
||||
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
|
||||
VERSION ?= 1.5.0
|
||||
VERSION ?= 1.9.0
|
||||
|
||||
# CHANNELS define the bundle channels used in the bundle.
|
||||
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
|
||||
@@ -48,10 +48,14 @@ ifeq ($(USE_IMAGE_DIGESTS), true)
|
||||
BUNDLE_GEN_FLAGS += --use-image-digests
|
||||
endif
|
||||
|
||||
# Set the Operator SDK version to use. By default, what is installed on the system is used.
|
||||
# This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit.
|
||||
OPERATOR_SDK_VERSION ?= v1.34.1
|
||||
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= 1password/onepassword-operator:latest
|
||||
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
|
||||
ENVTEST_K8S_VERSION = 1.24.2
|
||||
ENVTEST_K8S_VERSION = 1.29.1
|
||||
|
||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
@@ -60,6 +64,12 @@ else
|
||||
GOBIN=$(shell go env GOBIN)
|
||||
endif
|
||||
|
||||
# CONTAINER_TOOL defines the container tool to be used for building images.
|
||||
# Be aware that the target commands are only tested with Docker which is
|
||||
# scaffolded by default. However, you might want to replace it to use other
|
||||
# tools. (i.e. podman)
|
||||
CONTAINER_TOOL ?= docker
|
||||
|
||||
# Setting SHELL to bash allows bash commands to be executed by recipes.
|
||||
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
|
||||
SHELL = /usr/bin/env bash -o pipefail
|
||||
@@ -110,40 +120,37 @@ test: manifests generate fmt vet envtest ## Run tests.
|
||||
##@ Build
|
||||
|
||||
.PHONY: build
|
||||
build: generate fmt vet ## Build manager binary.
|
||||
go build -o bin/manager main.go
|
||||
build: manifests generate fmt vet ## Build manager binary.
|
||||
go build -o bin/manager cmd/main.go
|
||||
|
||||
.PHONY: run
|
||||
run: manifests generate fmt vet ## Run a controller from your host.
|
||||
go run ./main.go
|
||||
go run ./cmd/main.go
|
||||
|
||||
# If you wish built the manager image targeting other platforms you can use the --platform flag.
|
||||
# (i.e. docker build --platform linux/arm64 ). However, you must enable docker buildKit for it.
|
||||
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
|
||||
.PHONY: docker-build
|
||||
docker-build: test ## Build docker image with the manager.
|
||||
docker build -t ${IMG} .
|
||||
$(CONTAINER_TOOL) build -t ${IMG} .
|
||||
|
||||
.PHONY: docker-push
|
||||
docker-push: ## Push docker image with the manager.
|
||||
docker push ${IMG}
|
||||
$(CONTAINER_TOOL) push ${IMG}
|
||||
|
||||
# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple
|
||||
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
|
||||
# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/
|
||||
# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/
|
||||
# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=<myregistry/image:<tag>> than the export will fail)
|
||||
# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=<myregistry/image:<tag>> then the export will fail)
|
||||
# To properly provided solutions that supports more than one platform you should use this option.
|
||||
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
|
||||
.PHONY: docker-buildx
|
||||
docker-buildx: test ## Build and push docker image for the manager for cross-platform support
|
||||
# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
|
||||
sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
|
||||
- docker buildx create --name project-v3-builder
|
||||
docker buildx use project-v3-builder
|
||||
- docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross
|
||||
- docker buildx rm project-v3-builder
|
||||
rm Dockerfile.cross
|
||||
docker-buildx: ## Build and push docker image for the manager for cross-platform support
|
||||
- $(CONTAINER_TOOL) buildx create --name project-v3-builder
|
||||
$(CONTAINER_TOOL) buildx use project-v3-builder
|
||||
- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile .
|
||||
- $(CONTAINER_TOOL) buildx rm project-v3-builder
|
||||
|
||||
##@ Deployment
|
||||
|
||||
@@ -153,24 +160,24 @@ endif
|
||||
|
||||
.PHONY: install
|
||||
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
|
||||
$(KUSTOMIZE) build config/crd | kubectl apply -f -
|
||||
$(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f -
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
|
||||
$(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
|
||||
$(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -
|
||||
|
||||
.PHONY: set-namespace
|
||||
set-namespace:
|
||||
cd config/default && $(KUSTOMIZE) edit set namespace $(shell kubectl config view --minify -o jsonpath={..namespace})
|
||||
cd config/default && $(KUSTOMIZE) edit set namespace $(shell $(KUBECTL) config view --minify -o jsonpath={..namespace})
|
||||
|
||||
.PHONY: deploy
|
||||
deploy: manifests kustomize set-namespace ## Deploy controller to the K8s cluster specified in ~/.kube/config.
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
$(KUSTOMIZE) build config/default | kubectl apply -f -
|
||||
$(KUSTOMIZE) build config/default | $(KUBECTL) apply -f -
|
||||
|
||||
.PHONY: undeploy
|
||||
undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
|
||||
$(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
|
||||
$(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -
|
||||
|
||||
##@ Build Dependencies
|
||||
|
||||
@@ -180,36 +187,58 @@ $(LOCALBIN):
|
||||
mkdir -p $(LOCALBIN)
|
||||
|
||||
## Tool Binaries
|
||||
KUBECTL ?= kubectl
|
||||
KUSTOMIZE ?= $(LOCALBIN)/kustomize
|
||||
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
|
||||
ENVTEST ?= $(LOCALBIN)/setup-envtest
|
||||
|
||||
## Tool Versions
|
||||
KUSTOMIZE_VERSION ?= v4.5.7
|
||||
CONTROLLER_TOOLS_VERSION ?= v0.10.0
|
||||
KUSTOMIZE_VERSION ?= v5.3.0
|
||||
CONTROLLER_TOOLS_VERSION ?= v0.14.0
|
||||
|
||||
KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
|
||||
.PHONY: kustomize
|
||||
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
|
||||
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading.
|
||||
$(KUSTOMIZE): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/kustomize || { curl -s $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); }
|
||||
@if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \
|
||||
echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \
|
||||
rm -rf $(LOCALBIN)/kustomize; \
|
||||
fi
|
||||
test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) GO111MODULE=on go install sigs.k8s.io/kustomize/kustomize/v5@$(KUSTOMIZE_VERSION)
|
||||
|
||||
.PHONY: controller-gen
|
||||
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
|
||||
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten.
|
||||
$(CONTROLLER_GEN): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/controller-gen || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
|
||||
test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \
|
||||
GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
|
||||
|
||||
.PHONY: envtest
|
||||
envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
|
||||
$(ENVTEST): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
|
||||
|
||||
.PHONY: operator-sdk
|
||||
OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk
|
||||
operator-sdk: ## Download operator-sdk locally if necessary.
|
||||
ifeq (,$(wildcard $(OPERATOR_SDK)))
|
||||
ifeq (, $(shell which operator-sdk 2>/dev/null))
|
||||
@{ \
|
||||
set -e ;\
|
||||
mkdir -p $(dir $(OPERATOR_SDK)) ;\
|
||||
OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
|
||||
curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\
|
||||
chmod +x $(OPERATOR_SDK) ;\
|
||||
}
|
||||
else
|
||||
OPERATOR_SDK = $(shell which operator-sdk)
|
||||
endif
|
||||
endif
|
||||
|
||||
.PHONY: bundle
|
||||
bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files.
|
||||
operator-sdk generate kustomize manifests -q
|
||||
bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files.
|
||||
$(OPERATOR_SDK) generate kustomize manifests -q
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
|
||||
$(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS)
|
||||
operator-sdk bundle validate ./bundle
|
||||
$(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS)
|
||||
$(OPERATOR_SDK) bundle validate ./bundle
|
||||
|
||||
.PHONY: bundle-build
|
||||
bundle-build: ## Build the bundle image.
|
||||
|
6
PROJECT
6
PROJECT
@@ -1,6 +1,10 @@
|
||||
# Code generated by tool. DO NOT EDIT.
|
||||
# This file is used to track the info used to scaffold your project
|
||||
# and allow the plugins properly work.
|
||||
# More info: https://book.kubebuilder.io/reference/project-config.html
|
||||
domain: onepassword.com
|
||||
layout:
|
||||
- go.kubebuilder.io/v4-alpha
|
||||
- go.kubebuilder.io/v4
|
||||
plugins:
|
||||
manifests.sdk.operatorframework.io/v2: {}
|
||||
scorecard.sdk.operatorframework.io/v2: {}
|
||||
|
290
README.md
290
README.md
@@ -1,112 +1,33 @@
|
||||
# 1Password Connect Kubernetes Operator
|
||||
<!-- Image sourced from https://blog.1password.com/introducing-secrets-automation/ -->
|
||||
<img alt="" role="img" src="https://blog.1password.com/posts/2021/secrets-automation-launch/header.svg"/>
|
||||
|
||||
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.
|
||||
<div align="center">
|
||||
<h1>1Password Connect Kubernetes Operator</h1>
|
||||
<p>Integrate <a href="https://developer.1password.com/docs/connect">1Password Connect</a> with your Kubernetes Infrastructure</p>
|
||||
<a href="https://github.com/1Password/onepassword-operator#-get-started">
|
||||
<img alt="Get started" src="https://user-images.githubusercontent.com/45081667/226940040-16d3684b-60f4-4d95-adb2-5757a8f1bc15.png" height="37"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
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.
|
||||
The 1Password Connect Kubernetes Operator provides the ability to integrate Kubernetes Secrets with 1Password. The operator also handles autorestarting deployments when 1Password items are updated.
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quickstart for Deploying 1Password Connect to Kubernetes](#quickstart-for-deploying-1password-connect-to-kubernetes)
|
||||
- [Kubernetes Operator Deployment](#kubernetes-operator-deployment)
|
||||
- [Usage](#usage)
|
||||
- [Configuring Automatic Rolling Restarts of Deployments](#configuring-automatic-rolling-restarts-of-deployments)
|
||||
- [Development](#development)
|
||||
- [Security](#security)
|
||||
## ✨ Get started
|
||||
|
||||
## Prerequisites
|
||||
### 🚀 Quickstart
|
||||
|
||||
- [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://developer.1password.com/docs/connect/get-started/#step-1-set-up-a-secrets-automation-workflow)
|
||||
- [A `1password-credentials.json` file generated and a 1Password Connect API Token issues for the K8s Operator integration](https://developer.1password.com/docs/connect/get-started/#step-1-set-up-a-secrets-automation-workflow)
|
||||
## Quickstart for Deploying 1Password Connect to Kubernetes
|
||||
1. Add the [1Password Helm Chart](https://github.com/1Password/connect-helm-charts) to your repository.
|
||||
|
||||
If 1Password Connect is already running, you can skip this step.
|
||||
|
||||
There are options to deploy 1Password Connect:
|
||||
|
||||
- [Deploy with Helm](#deploy-with-helm)
|
||||
- [Deploy using the Connect Operator](#deploy-using-the-connect-operator)
|
||||
|
||||
#### 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
|
||||
|
||||
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=op-session
|
||||
```
|
||||
|
||||
Add the following environment variable to the onepassword-connect-operator container in `/config/manager/manager.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 current 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>)
|
||||
```
|
||||
|
||||
**Deploying the Operator**
|
||||
|
||||
An sample Deployment yaml can be found at `/config/manager/manager.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 current 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.
|
||||
|
||||
To deploy the operator, simply run the following command:
|
||||
|
||||
```shell
|
||||
make deploy
|
||||
```
|
||||
|
||||
**Undeploy Operator**
|
||||
2. Run the following command to install Connect and the 1Password Kubernetes Operator in your infrastructure:
|
||||
|
||||
```
|
||||
make undeploy
|
||||
helm install connect 1password/connect --set-file connect.credentials=1password-credentials-demo.json --set operator.create=true --set operator.token.value = <your connect token>
|
||||
```
|
||||
|
||||
## Usage
|
||||
3. Create a Kubernetes Secret from a 1Password item:
|
||||
|
||||
To create a Kubernetes Secret from a 1Password item, create a yaml file with the following
|
||||
|
||||
```yaml
|
||||
```
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem
|
||||
metadata:
|
||||
@@ -117,181 +38,28 @@ spec:
|
||||
|
||||
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:
|
||||
Check that the Kubernetes Secret has been generated:
|
||||
|
||||
```bash
|
||||
```
|
||||
kubectl get secret <secret_name>
|
||||
```
|
||||
|
||||
Note: Deleting the `OnePasswordItem` that you've created will automatically delete the created Kubernetes Secret.
|
||||
### 📄 Usage
|
||||
|
||||
To create a single Kubernetes Secret for a deployment, add the following annotations to the deployment metadata:
|
||||
Refer to the [Usage Guide](USAGEGUIDE.md) for documentation on how to deploy and use the 1Password Operator.
|
||||
|
||||
```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>"
|
||||
```
|
||||
## 💙 Community & Support
|
||||
|
||||
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.
|
||||
- File an [issue](https://github.com/1Password/onepassword-operator/issues) for bugs and feature requests.
|
||||
- Join the [Developer Slack workspace](https://join.slack.com/t/1password-devs/shared_invite/zt-1halo11ps-6o9pEv96xZ3LtX_VE0fJQA).
|
||||
- Subscribe to the [Developer Newsletter](https://1password.com/dev-subscribe/).
|
||||
|
||||
The contents of the Kubernetes secret will be key-value pairs in which the keys are the fields of the 1Password item and the values are the corresponding values stored in 1Password.
|
||||
In case of fields that store files, the file's contents will be used as the value.
|
||||
|
||||
Within an item, if both a field storing a file and a field of another type have the same name, the file field will be ignored and the other field will take precedence.
|
||||
|
||||
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 restart 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 restart 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 restart settings on the deployment will be used.
|
||||
|
||||
<!--
|
||||
## Getting Started
|
||||
You’ll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster.
|
||||
**Note:** Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster `kubectl cluster-info` shows).
|
||||
|
||||
### Running on the cluster
|
||||
1. Install Instances of Custom Resources:
|
||||
|
||||
```sh
|
||||
kubectl apply -f config/samples/
|
||||
```
|
||||
|
||||
2. Deploy the controller to the cluster with the image specified by `IMG`:
|
||||
|
||||
```sh
|
||||
make deploy IMG=<some-registry>/onepassword-operator:tag
|
||||
```
|
||||
|
||||
### Uninstall CRDs
|
||||
To delete the CRDs from the cluster:
|
||||
|
||||
```sh
|
||||
make uninstall
|
||||
```
|
||||
|
||||
### Undeploy controller
|
||||
UnDeploy the controller to the cluster:
|
||||
|
||||
```sh
|
||||
make undeploy
|
||||
```
|
||||
-->
|
||||
|
||||
## Development
|
||||
|
||||
### How it works
|
||||
|
||||
This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/)
|
||||
|
||||
It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/)
|
||||
which provides a reconcile function responsible for synchronizing resources untile the desired state is reached on the cluster
|
||||
|
||||
### Test It Out
|
||||
|
||||
1. Install the CRDs into the cluster:
|
||||
|
||||
```sh
|
||||
make install
|
||||
```
|
||||
|
||||
2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running):
|
||||
|
||||
```sh
|
||||
make run
|
||||
```
|
||||
|
||||
**NOTE:** You can also run this in one step by running: `make install run`
|
||||
|
||||
### Modifying the API definitions
|
||||
|
||||
If you are editing the API definitions, generate the manifests such as CRs or CRDs using:
|
||||
|
||||
```sh
|
||||
make manifests
|
||||
```
|
||||
|
||||
**NOTE:** Run `make --help` for more information on all potential `make` targets
|
||||
|
||||
More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
|
||||
|
||||
## 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).
|
||||
Please file requests by sending an email to bugbounty@agilebits.com.
|
||||
|
221
USAGEGUIDE.md
Normal file
221
USAGEGUIDE.md
Normal file
@@ -0,0 +1,221 @@
|
||||
<img alt="" role="img" src="https://blog.1password.com/posts/2021/secrets-automation-launch/header.svg"/>
|
||||
<div align="center">
|
||||
<h1>Usage Guide</h1>
|
||||
</div>
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Configuration Options](#configuration-options)
|
||||
2. [Use Kubernetes Operator with Service Account](#use-kubernetes-operator-with-service-account)
|
||||
- [Create a Service Account](#1-create-a-service-account)
|
||||
- [Create a Kubernetes secret](#2-create-a-kubernetes-secret-for-the-service-account)
|
||||
- [Deploy the Operator](#3-deploy-the-operator)
|
||||
3. [Use Kubernetes Operator with Connect](#use-kubernetes-operator-with-connect)
|
||||
- [Deploy with Helm](#1-deploy-with-helm)
|
||||
- [Deploy manually](#2-deploy-manually)
|
||||
4. [Logging level](#logging-level)
|
||||
5. [Usage examples](#usage-examples)
|
||||
6. [How 1Password Items Map to Kubernetes Secrets](#how-1password-items-map-to-kubernetes-secrets)
|
||||
7. [Configuring Automatic Rolling Restarts of Deployments](#configuring-automatic-rolling-restarts-of-deployments)
|
||||
8. [Development](#development)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Configuration options
|
||||
There are 2 ways 1Password Operator can talk to 1Password servers:
|
||||
- [1Password Service Accounts](https://developer.1password.com/docs/service-accounts)
|
||||
- [1Password Connect](https://developer.1password.com/docs/connect/)
|
||||
|
||||
---
|
||||
|
||||
## Use Kubernetes Operator with Service Account
|
||||
|
||||
### 1. [Create a service account](https://developer.1password.com/docs/service-accounts/get-started#create-a-service-account)
|
||||
### 2. Create a Kubernetes secret for the Service Account
|
||||
- Set `OP_SERVICE_ACCOUNT_TOKEN` environment variable to the service account token you created in the previous step. This token will be used by the operator to access 1Password items.
|
||||
- Create Kubernetes secret:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic onepassword-service-account-token --from-literal=token="$OP_SERVICE_ACCOUNT_TOKEN"
|
||||
```
|
||||
|
||||
### 3. Deploy the Operator
|
||||
|
||||
An sample Deployment yaml can be found at `/config/manager/manager.yaml`.
|
||||
To use Operator with Service Account, you need to set the `OP_SERVICE_ACCOUNT_TOKEN` environment variable in the `/config/manager/manager.yaml`. And remove `OP_CONNECT_TOKEN` and `OP_CONNECT_HOST` environment variables.
|
||||
|
||||
To further configure the 1Password Kubernetes Operator the following Environment variables can be set in the operator yaml:
|
||||
|
||||
- **OP_SERVICE_ACCOUNT_TOKEN** *(required)*: Specifies Service Account token within Kubernetes to access the 1Password items.
|
||||
- **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.
|
||||
- **AUTO_RESTART** (default: false): If set to true, the operator will restart any deployment using a secret from 1Password. 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.
|
||||
|
||||
To deploy the operator, simply run the following command:
|
||||
|
||||
```shell
|
||||
make deploy
|
||||
```
|
||||
|
||||
**Undeploy Operator**
|
||||
|
||||
```
|
||||
make undeploy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Kubernetes Operator with Connect
|
||||
|
||||
### 1. [Deploy with Helm](https://developer.1password.com/docs/k8s/operator/?deployment-type=helm#helm-step-1)
|
||||
### 2. [Deploy manually](https://developer.1password.com/docs/k8s/operator/?deployment-type=manual#manual-step-1)
|
||||
|
||||
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 current 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.
|
||||
|
||||
---
|
||||
|
||||
## Logging level
|
||||
You can set the logging level by setting `--zap-log-level` as an arg on the containers to either `debug`, `info` or `error`. The default value is `debug`.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
....
|
||||
containers:
|
||||
- command:
|
||||
- /manager
|
||||
args:
|
||||
- --leader-elect
|
||||
- --zap-log-level=info
|
||||
image: 1password/onepassword-operator:latest
|
||||
....
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage examples
|
||||
Find usage [examples](https://developer.1password.com/docs/k8s/operator/?deployment-type=manual#usage-examples) on 1Password developer documentation.
|
||||
|
||||
---
|
||||
|
||||
## How 1Password Items Map to Kubernetes Secrets
|
||||
|
||||
The contents of the Kubernetes secret will be key-value pairs in which the keys are the fields of the 1Password item and the values are the corresponding values stored in 1Password.
|
||||
In case of fields that store files, the file's contents will be used as the value.
|
||||
|
||||
Within an item, if both a field storing a file and a field of another type have the same name, the file field will be ignored and the other field will take precedence.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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:
|
||||
- Operator level
|
||||
- Per-namespace
|
||||
- Per-deployment
|
||||
|
||||
**Operator Level**: This method allows for managing auto restarts on all deployments within the namespaces watched by operator. Auto restarts can be enabled by setting the environment 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 restart 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 restart 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 restart settings on the deployment will be used.
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### How it works
|
||||
|
||||
This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/)
|
||||
|
||||
It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/)
|
||||
which provides a reconcile function responsible for synchronizing resources until the desired state is reached on the cluster
|
||||
|
||||
### Test It Out
|
||||
|
||||
1. Install the CRDs into the cluster:
|
||||
|
||||
```sh
|
||||
make install
|
||||
```
|
||||
|
||||
2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running):
|
||||
|
||||
```sh
|
||||
make run
|
||||
```
|
||||
|
||||
**NOTE:** You can also run this in one step by running: `make install run`
|
||||
|
||||
### Modifying the API definitions
|
||||
|
||||
If you are editing the API definitions, generate the manifests such as CRs or CRDs using:
|
||||
|
||||
```sh
|
||||
make manifests
|
||||
```
|
||||
|
||||
**NOTE:** Run `make --help` for more information on all potential `make` targets
|
||||
|
||||
More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 1Password
|
||||
Copyright (c) 2020-2024 1Password
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 1Password
|
||||
Copyright (c) 2020-2024 1Password
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@@ -1,10 +1,9 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 1Password
|
||||
Copyright (c) 2020-2024 1Password
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 1Password
|
||||
Copyright (c) 2020-2024 1Password
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -25,6 +25,7 @@ SOFTWARE.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -35,8 +36,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
|
||||
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
|
||||
// to ensure that exec-entrypoint and run can make use of them.
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
@@ -44,14 +43,17 @@ import (
|
||||
k8sruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
onepasswordcomv1 "github.com/1Password/onepassword-operator/api/v1"
|
||||
"github.com/1Password/onepassword-operator/controllers"
|
||||
"github.com/1Password/onepassword-operator/internal/controller"
|
||||
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||
opclient "github.com/1Password/onepassword-operator/pkg/onepassword/client"
|
||||
"github.com/1Password/onepassword-operator/pkg/utils"
|
||||
"github.com/1Password/onepassword-operator/version"
|
||||
//+kubebuilder:scaffold:imports
|
||||
@@ -111,6 +113,9 @@ func main() {
|
||||
|
||||
printVersion()
|
||||
|
||||
// Create a root context that will be cancelled on termination signals
|
||||
ctx := ctrl.SetupSignalHandler()
|
||||
|
||||
watchNamespace, err := getWatchNamespace()
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to get WatchNamespace, "+
|
||||
@@ -125,20 +130,23 @@ func main() {
|
||||
|
||||
options := ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Namespace: watchNamespace,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
Port: 9443,
|
||||
Metrics: metricsserver.Options{BindAddress: metricsAddr},
|
||||
HealthProbeBindAddress: probeAddr,
|
||||
LeaderElection: enableLeaderElection,
|
||||
LeaderElectionID: "c26807fd.onepassword.com",
|
||||
}
|
||||
|
||||
// Add support for MultiNamespace set in WATCH_NAMESPACE (e.g ns1,ns2)
|
||||
if strings.Contains(watchNamespace, ",") {
|
||||
setupLog.Info("manager set up with multiple namespaces", "namespaces", watchNamespace)
|
||||
// configure cluster-scoped with MultiNamespacedCacheBuilder
|
||||
options.Namespace = ""
|
||||
options.NewCache = cache.MultiNamespacedCacheBuilder(strings.Split(watchNamespace, ","))
|
||||
if watchNamespace != "" {
|
||||
namespaces := strings.Split(watchNamespace, ",")
|
||||
namespaceMap := make(map[string]cache.Config)
|
||||
for _, namespace := range namespaces {
|
||||
namespaceMap[namespace] = cache.Config{}
|
||||
}
|
||||
options.NewCache = func(config *rest.Config, opts cache.Options) (cache.Cache, error) {
|
||||
opts.DefaultNamespaces = namespaceMap
|
||||
return cache.New(config, opts)
|
||||
}
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options)
|
||||
@@ -148,26 +156,29 @@ func main() {
|
||||
}
|
||||
|
||||
// Setup One Password Client
|
||||
opConnectClient, err := connect.NewClientFromEnvironment()
|
||||
opClient, err := opclient.NewFromEnvironment(ctx, opclient.Config{
|
||||
Logger: setupLog,
|
||||
Version: version.OperatorVersion,
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to create Connect client")
|
||||
setupLog.Error(err, "unable to create 1Password client")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&controllers.OnePasswordItemReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
OpConnectClient: opConnectClient,
|
||||
if err = (&controller.OnePasswordItemReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
OpClient: opClient,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "OnePasswordItem")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
r, _ := regexp.Compile(annotationRegExpString)
|
||||
if err = (&controllers.DeploymentReconciler{
|
||||
if err = (&controller.DeploymentReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
OpConnectClient: opConnectClient,
|
||||
OpClient: opClient,
|
||||
OpAnnotationRegExp: r,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Deployment")
|
||||
@@ -178,10 +189,10 @@ func main() {
|
||||
//Setup 1PasswordConnect
|
||||
if shouldManageConnect() {
|
||||
setupLog.Info("Automated Connect Management Enabled")
|
||||
go func() {
|
||||
go func(ctx context.Context) {
|
||||
connectStarted := false
|
||||
for connectStarted == false {
|
||||
err := op.SetupConnect(mgr.GetClient(), deploymentNamespace)
|
||||
err := op.SetupConnect(ctx, mgr.GetClient(), deploymentNamespace)
|
||||
// Cache Not Started is an acceptable error. Retry until cache is started.
|
||||
if err != nil && !errors.Is(err, &cache.ErrCacheNotStarted{}) {
|
||||
setupLog.Error(err, "")
|
||||
@@ -191,29 +202,29 @@ func main() {
|
||||
connectStarted = true
|
||||
}
|
||||
}
|
||||
}()
|
||||
}(ctx)
|
||||
} else {
|
||||
setupLog.Info("Automated Connect Management Disabled")
|
||||
}
|
||||
|
||||
// Setup update secrets task
|
||||
updatedSecretsPoller := op.NewManager(mgr.GetClient(), opConnectClient, shouldAutoRestartDeployments())
|
||||
updatedSecretsPoller := op.NewManager(mgr.GetClient(), opClient, shouldAutoRestartDeployments())
|
||||
done := make(chan bool)
|
||||
ticker := time.NewTicker(getPollingIntervalForUpdatingSecrets())
|
||||
go func() {
|
||||
go func(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
ticker.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
err := updatedSecretsPoller.UpdateKubernetesSecretsTask()
|
||||
err := updatedSecretsPoller.UpdateKubernetesSecretsTask(ctx)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "error running update kubernetes secret task")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}(ctx)
|
||||
|
||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
setupLog.Error(err, "unable to set up health check")
|
||||
@@ -225,7 +236,7 @@ func main() {
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
if err := mgr.Start(ctx); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
os.Exit(1)
|
||||
}
|
@@ -12,6 +12,8 @@ spec:
|
||||
app: onepassword-connect
|
||||
version: "1.0.0"
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
volumes:
|
||||
- name: shared-data
|
||||
emptyDir: {}
|
||||
@@ -32,9 +34,12 @@ spec:
|
||||
containers:
|
||||
- name: connect-api
|
||||
image: 1password/connect-api:latest
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
resources:
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
requests:
|
||||
cpu: "0.2"
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
@@ -49,9 +54,12 @@ spec:
|
||||
name: shared-data
|
||||
- name: connect-sync
|
||||
image: 1password/connect-sync:latest
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
resources:
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
requests:
|
||||
cpu: "0.2"
|
||||
ports:
|
||||
- containerPort: 8081
|
||||
|
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.9.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: onepassworditems.onepassword.com
|
||||
spec:
|
||||
group: onepassword.com
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
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'
|
||||
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'
|
||||
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
|
||||
|
@@ -5,17 +5,19 @@ resources:
|
||||
- bases/onepassword.com_onepassworditems.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
patchesStrategicMerge:
|
||||
patches:
|
||||
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
|
||||
# patches here are for enabling the conversion webhook for each CRD
|
||||
#- patches/webhook_in_onepassworditems.yaml
|
||||
#- path: patches/webhook_in_onepassworditems.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizewebhookpatch
|
||||
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
|
||||
# patches here are for enabling the CA injection for each CRD
|
||||
#- patches/cainjection_in_onepassworditems.yaml
|
||||
#- path: patches/cainjection_in_onepassworditems.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizecainjectionpatch
|
||||
|
||||
# [WEBHOOK] To enable webhook, uncomment the following section
|
||||
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||
configurations:
|
||||
- kustomizeconfig.yaml
|
||||
|
||||
#configurations:
|
||||
#- kustomizeconfig.yaml
|
||||
|
@@ -1,3 +1,6 @@
|
||||
# Adds namespace to all resources.
|
||||
# namespace: onepassword-connect-operator
|
||||
|
||||
# Value of this field is prepended to the
|
||||
# names of all resources, e.g. a deployment named
|
||||
# "wordpress" becomes "alices-wordpress".
|
||||
@@ -23,24 +26,20 @@ resources:
|
||||
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
|
||||
#- ../prometheus
|
||||
|
||||
patchesStrategicMerge:
|
||||
patches:
|
||||
# Protect the /metrics endpoint by putting it behind auth.
|
||||
# If you want your controller-manager to expose the /metrics
|
||||
# endpoint w/o any authn/z, please comment the following line.
|
||||
- manager_auth_proxy_patch.yaml
|
||||
|
||||
# Mount the controller config file for loading manager configurations
|
||||
# through a ComponentConfig type
|
||||
#- manager_config_patch.yaml
|
||||
- path: manager_auth_proxy_patch.yaml
|
||||
|
||||
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
|
||||
# crd/kustomization.yaml
|
||||
#- manager_webhook_patch.yaml
|
||||
#- path: manager_webhook_patch.yaml
|
||||
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
|
||||
# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
|
||||
# 'CERTMANAGER' needs to be enabled to use ca injection
|
||||
#- webhookcainjection_patch.yaml
|
||||
#- path: webhookcainjection_patch.yaml
|
||||
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
|
||||
# Uncomment the following replacements to add the cert-manager CA injection annotations
|
||||
|
@@ -8,6 +8,8 @@ metadata:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
containers:
|
||||
- name: kube-rbac-proxy
|
||||
securityContext:
|
||||
@@ -15,7 +17,7 @@ spec:
|
||||
capabilities:
|
||||
drop:
|
||||
- "ALL"
|
||||
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0
|
||||
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.15.0
|
||||
args:
|
||||
- "--secure-listen-address=0.0.0.0:8443"
|
||||
- "--upstream=http://127.0.0.1:8080/"
|
||||
|
@@ -8,13 +8,3 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: manager
|
||||
args:
|
||||
- "--config=controller_manager_config.yaml"
|
||||
volumeMounts:
|
||||
- name: manager-config
|
||||
mountPath: /controller_manager_config.yaml
|
||||
subPath: controller_manager_config.yaml
|
||||
volumes:
|
||||
- name: manager-config
|
||||
configMap:
|
||||
name: manager-config
|
||||
|
@@ -1,21 +0,0 @@
|
||||
apiVersion: controller-runtime.sigs.k8s.io/v1alpha1
|
||||
kind: ControllerManagerConfig
|
||||
health:
|
||||
healthProbeBindAddress: :8081
|
||||
metrics:
|
||||
bindAddress: 127.0.0.1:8080
|
||||
webhook:
|
||||
port: 9443
|
||||
leaderElection:
|
||||
leaderElect: true
|
||||
resourceName: c26807fd.onepassword.com
|
||||
# leaderElectionReleaseOnCancel defines if the leader should step down volume
|
||||
# when the Manager ends. This requires the binary to immediately end when the
|
||||
# Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
|
||||
# speeds up voluntary leader transitions as the new leader don't have to wait
|
||||
# LeaseDuration time first.
|
||||
# In the default scaffold provided, the program ends immediately after
|
||||
# the manager stops, so would be fine to enable this option. However,
|
||||
# if you are doing or is intended to do any operation such as perform cleanups
|
||||
# after the manager stops then its usage might be unsafe.
|
||||
# leaderElectionReleaseOnCancel: true
|
@@ -1,10 +1,8 @@
|
||||
resources:
|
||||
- manager.yaml
|
||||
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
|
||||
configMapGenerator:
|
||||
- name: manager-config
|
||||
files:
|
||||
- controller_manager_config.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
images:
|
||||
- name: controller
|
||||
newName: 1password/onepassword-operator
|
||||
newTag: latest
|
||||
|
@@ -1,14 +1,34 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: onepassword-connect-operator
|
||||
app.kubernetes.io/name: namespace
|
||||
app.kubernetes.io/instance: system
|
||||
app.kubernetes.io/component: manager
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: system
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: onepassword-connect-operator
|
||||
namespace: system
|
||||
labels:
|
||||
name: onepassword-connect-operator
|
||||
control-plane: controller-manager
|
||||
app.kubernetes.io/name: deployment
|
||||
app.kubernetes.io/instance: controller-manager
|
||||
app.kubernetes.io/component: manager
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
name: onepassword-connect-operator
|
||||
control-plane: onepassword-connect-operator
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
@@ -16,7 +36,28 @@ spec:
|
||||
kubectl.kubernetes.io/default-container: manager
|
||||
labels:
|
||||
name: onepassword-connect-operator
|
||||
control-plane: onepassword-connect-operator
|
||||
spec:
|
||||
# TODO(user): Uncomment the following code to configure the nodeAffinity expression
|
||||
# according to the platforms which are supported by your solution.
|
||||
# It is considered best practice to support multiple architectures. You can
|
||||
# build your manager image using the makefile target docker-buildx.
|
||||
# affinity:
|
||||
# nodeAffinity:
|
||||
# requiredDuringSchedulingIgnoredDuringExecution:
|
||||
# nodeSelectorTerms:
|
||||
# - matchExpressions:
|
||||
# - key: kubernetes.io/arch
|
||||
# operator: In
|
||||
# values:
|
||||
# - amd64
|
||||
# - arm64
|
||||
# - ppc64le
|
||||
# - s390x
|
||||
# - key: kubernetes.io/os
|
||||
# operator: In
|
||||
# values:
|
||||
# - linux
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
# TODO(user): For common cases that do not require escalating privileges
|
||||
@@ -34,25 +75,33 @@ spec:
|
||||
image: 1password/onepassword-operator:latest
|
||||
name: manager
|
||||
env:
|
||||
- name: WATCH_NAMESPACE
|
||||
value: "default"
|
||||
- name: OPERATOR_NAME
|
||||
value: "onepassword-connect-operator"
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: OPERATOR_NAME
|
||||
value: "onepassword-connect-operator"
|
||||
- name: OP_CONNECT_HOST
|
||||
value: "http://onepassword-connect:8080"
|
||||
- name: WATCH_NAMESPACE
|
||||
value: "default"
|
||||
- name: POLLING_INTERVAL
|
||||
value: "10"
|
||||
- name: AUTO_RESTART
|
||||
value: "false"
|
||||
- name: OP_CONNECT_HOST
|
||||
value: "http://onepassword-connect:8080"
|
||||
- name: OP_CONNECT_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: onepassword-token
|
||||
key: token
|
||||
- name: AUTO_RESTART
|
||||
- name: MANAGE_CONNECT
|
||||
value: "false"
|
||||
# Uncomment the following lines to enable service account token and comment out the OP_CONNECT_TOKEN, OP_CONNECT_HOST and MANAGE_CONNECT env vars.
|
||||
# - name: OP_SERVICE_ACCOUNT_TOKEN
|
||||
# valueFrom:
|
||||
# secretKeyRef:
|
||||
# name: onepassword-service-account-token
|
||||
# key: token
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
@@ -75,9 +124,9 @@ spec:
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 128Mi
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
serviceAccountName: onepassword-connect-operator
|
||||
terminationGracePeriodSeconds: 10
|
||||
|
@@ -1,10 +1,16 @@
|
||||
|
||||
# Prometheus Monitor Service (Metrics)
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
labels:
|
||||
name: onepassword-connect-operator
|
||||
control-plane: onepassword-connect-operator
|
||||
app.kubernetes.io/name: servicemonitor
|
||||
app.kubernetes.io/instance: controller-manager-metrics-monitor
|
||||
app.kubernetes.io/component: metrics
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: onepassword-connect-operator-metrics-monitor
|
||||
namespace: system
|
||||
spec:
|
||||
@@ -18,3 +24,4 @@ spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
name: onepassword-connect-operator
|
||||
control-plane: onepassword-connect-operator
|
||||
|
@@ -1,6 +1,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: clusterrole
|
||||
app.kubernetes.io/instance: metrics-reader
|
||||
app.kubernetes.io/component: kube-rbac-proxy
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: metrics-reader
|
||||
rules:
|
||||
- nonResourceURLs:
|
||||
|
@@ -1,6 +1,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: clusterrole
|
||||
app.kubernetes.io/instance: proxy-role
|
||||
app.kubernetes.io/component: kube-rbac-proxy
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: proxy-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
|
@@ -1,6 +1,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: clusterrolebinding
|
||||
app.kubernetes.io/instance: proxy-rolebinding
|
||||
app.kubernetes.io/component: kube-rbac-proxy
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: proxy-rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
@@ -3,6 +3,13 @@ kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
name: onepassword-connect-operator
|
||||
control-plane: onepassword-connect-operator
|
||||
app.kubernetes.io/name: service
|
||||
app.kubernetes.io/instance: controller-manager-metrics-service
|
||||
app.kubernetes.io/component: kube-rbac-proxy
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: onepassword-connect-operator-metrics-service
|
||||
namespace: system
|
||||
spec:
|
||||
@@ -13,3 +20,4 @@ spec:
|
||||
targetPort: https
|
||||
selector:
|
||||
name: onepassword-connect-operator
|
||||
control-plane: onepassword-connect-operator
|
||||
|
@@ -2,6 +2,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: role
|
||||
app.kubernetes.io/instance: leader-election-role
|
||||
app.kubernetes.io/component: rbac
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: leader-election-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
|
@@ -1,6 +1,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: rolebinding
|
||||
app.kubernetes.io/instance: leader-election-rolebinding
|
||||
app.kubernetes.io/component: rbac
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: leader-election-rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
@@ -2,6 +2,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: clusterrole
|
||||
app.kubernetes.io/instance: onepassworditem-editor-role
|
||||
app.kubernetes.io/component: rbac
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: onepassworditem-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
|
@@ -2,6 +2,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: clusterrole
|
||||
app.kubernetes.io/instance: onepassworditem-viewer-role
|
||||
app.kubernetes.io/component: rbac
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: onepassworditem-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
|
@@ -2,7 +2,6 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: manager-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
@@ -87,6 +86,15 @@ rules:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- update
|
||||
- apiGroups:
|
||||
- monitoring.coreos.com
|
||||
resources:
|
||||
|
@@ -1,6 +1,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: clusterrolebinding
|
||||
app.kubernetes.io/instance: manager-rolebinding
|
||||
app.kubernetes.io/component: rbac
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: manager-rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
@@ -1,5 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: serviceaccount
|
||||
app.kubernetes.io/instance: controller-manager-sa
|
||||
app.kubernetes.io/component: rbac
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: onepassword-connect-operator
|
||||
namespace: system
|
||||
|
@@ -1,6 +1,12 @@
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: onepassworditem
|
||||
app.kubernetes.io/instance: onepassworditem-sample
|
||||
app.kubernetes.io/part-of: onepassword-connect-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
app.kubernetes.io/created-by: onepassword-connect-operator
|
||||
name: onepassworditem-sample
|
||||
spec:
|
||||
itemPath: "vaults/<vault_id>/items/<item_id>"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- basic-check-spec
|
||||
image: quay.io/operator-framework/scorecard-test:v1.23.0
|
||||
image: quay.io/operator-framework/scorecard-test:v1.33.0
|
||||
labels:
|
||||
suite: basic
|
||||
test: basic-check-spec-test
|
||||
|
@@ -4,7 +4,7 @@
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- olm-bundle-validation
|
||||
image: quay.io/operator-framework/scorecard-test:v1.23.0
|
||||
image: quay.io/operator-framework/scorecard-test:v1.33.0
|
||||
labels:
|
||||
suite: olm
|
||||
test: olm-bundle-validation-test
|
||||
@@ -14,7 +14,7 @@
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- olm-crds-have-validation
|
||||
image: quay.io/operator-framework/scorecard-test:v1.23.0
|
||||
image: quay.io/operator-framework/scorecard-test:v1.33.0
|
||||
labels:
|
||||
suite: olm
|
||||
test: olm-crds-have-validation-test
|
||||
@@ -24,7 +24,7 @@
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- olm-crds-have-resources
|
||||
image: quay.io/operator-framework/scorecard-test:v1.23.0
|
||||
image: quay.io/operator-framework/scorecard-test:v1.33.0
|
||||
labels:
|
||||
suite: olm
|
||||
test: olm-crds-have-resources-test
|
||||
@@ -34,7 +34,7 @@
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- olm-spec-descriptors
|
||||
image: quay.io/operator-framework/scorecard-test:v1.23.0
|
||||
image: quay.io/operator-framework/scorecard-test:v1.33.0
|
||||
labels:
|
||||
suite: olm
|
||||
test: olm-spec-descriptors-test
|
||||
@@ -44,7 +44,7 @@
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- olm-status-descriptors
|
||||
image: quay.io/operator-framework/scorecard-test:v1.23.0
|
||||
image: quay.io/operator-framework/scorecard-test:v1.33.0
|
||||
labels:
|
||||
suite: olm
|
||||
test: olm-status-descriptors-test
|
||||
|
121
go.mod
121
go.mod
@@ -1,87 +1,92 @@
|
||||
module github.com/1Password/onepassword-operator
|
||||
|
||||
go 1.18
|
||||
go 1.24
|
||||
|
||||
toolchain go1.24.4
|
||||
|
||||
require (
|
||||
github.com/1Password/connect-sdk-go v1.5.0
|
||||
github.com/onsi/ginkgo/v2 v2.1.6
|
||||
github.com/onsi/gomega v1.20.2
|
||||
github.com/stretchr/testify v1.8.0
|
||||
k8s.io/api v0.25.3
|
||||
k8s.io/apimachinery v0.25.3
|
||||
k8s.io/client-go v0.25.3
|
||||
k8s.io/kubectl v0.25.0
|
||||
sigs.k8s.io/controller-runtime v0.13.0
|
||||
github.com/1Password/connect-sdk-go v1.5.3
|
||||
github.com/1password/onepassword-sdk-go v0.3.1
|
||||
github.com/onsi/ginkgo/v2 v2.14.0
|
||||
github.com/onsi/gomega v1.30.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
k8s.io/api v0.29.3
|
||||
k8s.io/apimachinery v0.29.3
|
||||
k8s.io/client-go v0.29.3
|
||||
k8s.io/kubectl v0.29.0
|
||||
sigs.k8s.io/controller-runtime v0.17.2
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.10.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/zapr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
|
||||
github.com/extism/go-sdk v1.7.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/gnostic v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/prometheus/client_golang v1.19.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.0 // indirect
|
||||
github.com/prometheus/common v0.51.1 // indirect
|
||||
github.com/prometheus/procfs v0.13.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/net v0.1.0 // indirect
|
||||
golang.org/x/oauth2 v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/term v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/time v0.1.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/oauth2 v0.18.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.25.3 // indirect
|
||||
k8s.io/component-base v0.25.3 // indirect
|
||||
k8s.io/klog/v2 v2.80.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.29.3 // indirect
|
||||
k8s.io/component-base v0.29.3 // indirect
|
||||
k8s.io/klog/v2 v2.120.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240322212309-b815d8309940 // indirect
|
||||
k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
747
go.sum
747
go.sum
@@ -1,711 +1,238 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v1.10.0 h1:aoLIYaA1fX3ywihqpBk2APQKOo20nXsp1GEZQbx5Jk4=
|
||||
cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/1Password/connect-sdk-go v1.5.0 h1:F0WJcLSzGg3iXEDY49/ULdszYKsQLGTzn+2cyYXqiyk=
|
||||
github.com/1Password/connect-sdk-go v1.5.0/go.mod h1:TdynFeyvaRoackENbJ8RfJokH+WAowAu1MLmUbdMq6s=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM=
|
||||
github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
|
||||
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/1Password/connect-sdk-go v1.5.3 h1:KyjJ+kCKj6BwB2Y8tPM1Ixg5uIS6HsB0uWA8U38p/Uk=
|
||||
github.com/1Password/connect-sdk-go v1.5.3/go.mod h1:5rSymY4oIYtS4G3t0oMkGAXBeoYiukV3vkqlnEjIDJs=
|
||||
github.com/1password/onepassword-sdk-go v0.3.1 h1:dz0LrYuIh/HrZ7rxr8NMymikNLBIXhyj4NBmo5Tdamc=
|
||||
github.com/1password/onepassword-sdk-go v0.3.1/go.mod h1:kssODrGGqHtniqPR91ZPoCMEo79mKulKat7RaD1bunk=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/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/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
|
||||
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE=
|
||||
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
|
||||
github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
|
||||
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
|
||||
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
||||
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
|
||||
github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/extism/go-sdk v1.7.0 h1:yHbSa2JbcF60kjGsYiGEOcClfbknqCJchyh9TRibFWo=
|
||||
github.com/extism/go-sdk v1.7.0/go.mod h1:Dhuc1qcD0aqjdqJ3ZDyGdkZPEj/EHKVjbE4P+1XRMqc=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
||||
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
|
||||
github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
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 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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1DU8ngI+aef9ZhAhNGQhcRTrWxVeG07F+c/Rw=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU=
|
||||
github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||
github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY=
|
||||
github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
|
||||
github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY=
|
||||
github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
|
||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||
github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw=
|
||||
github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q=
|
||||
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
|
||||
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
|
||||
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc=
|
||||
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y=
|
||||
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
|
||||
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ=
|
||||
k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI=
|
||||
k8s.io/apiextensions-apiserver v0.25.3 h1:bfI4KS31w2f9WM1KLGwnwuVlW3RSRPuIsfNF/3HzR0k=
|
||||
k8s.io/apiextensions-apiserver v0.25.3/go.mod h1:ZJqwpCkxIx9itilmZek7JgfUAM0dnTsA48I4krPqRmo=
|
||||
k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc=
|
||||
k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
|
||||
k8s.io/client-go v0.25.3 h1:oB4Dyl8d6UbfDHD8Bv8evKylzs3BXzzufLiO27xuPs0=
|
||||
k8s.io/client-go v0.25.3/go.mod h1:t39LPczAIMwycjcXkVc+CB+PZV69jQuNx4um5ORDjQA=
|
||||
k8s.io/component-base v0.25.3 h1:UrsxciGdrCY03ULT1h/S/gXFCOPnLhUVwSyx+hM/zq4=
|
||||
k8s.io/component-base v0.25.3/go.mod h1:WYoS8L+IlTZgU7rhAl5Ctpw0WdMxDfCC5dkxcEFa/TI=
|
||||
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
|
||||
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
|
||||
k8s.io/kubectl v0.25.0 h1:/Wn1cFqo8ik3iee1EvpxYre3bkWsGLXzLQI6uCCAkQc=
|
||||
k8s.io/kubectl v0.25.0/go.mod h1:n16ULWsOl2jmQpzt2o7Dud1t4o0+Y186ICb4O+GwKAU=
|
||||
k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 h1:cTdVh7LYu82xeClmfzGtgyspNh6UxpwLWGi8R4sspNo=
|
||||
k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/controller-runtime v0.13.0 h1:iqa5RNciy7ADWnIc8QxCbOX5FEKVR3uxVxKHRMc2WIQ=
|
||||
sigs.k8s.io/controller-runtime v0.13.0/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
|
||||
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
|
||||
k8s.io/apiextensions-apiserver v0.29.3 h1:9HF+EtZaVpFjStakF4yVufnXGPRppWFEQ87qnO91YeI=
|
||||
k8s.io/apiextensions-apiserver v0.29.3/go.mod h1:po0XiY5scnpJfFizNGo6puNU6Fq6D70UJY2Cb2KwAVc=
|
||||
k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
|
||||
k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU=
|
||||
k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
|
||||
k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
|
||||
k8s.io/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo=
|
||||
k8s.io/component-base v0.29.3/go.mod h1:Yuj33XXjuOk2BAaHsIGHhCKZQAgYKhqIxIjIr2UXYio=
|
||||
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
|
||||
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20240322212309-b815d8309940 h1:qVoMaQV5t62UUvHe16Q3eb2c5HPzLHYzsi0Tu/xLndo=
|
||||
k8s.io/kube-openapi v0.0.0-20240322212309-b815d8309940/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
|
||||
k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI=
|
||||
k8s.io/kubectl v0.29.0/go.mod h1:0jMjGWIcMIQzmUaMgAzhSELv5WtHo2a8pq67DtviAJs=
|
||||
k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY=
|
||||
k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.17.2 h1:FwHwD1CTUemg0pW2otk7/U5/i5m2ymzvOXdbeGOUvw0=
|
||||
sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 1Password
|
||||
Copyright (c) 2020-2024 1Password
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 1Password
|
||||
Copyright (c) 2020-2024 1Password
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,18 +22,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
|
||||
"github.com/1Password/onepassword-operator/pkg/logs"
|
||||
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||
opclient "github.com/1Password/onepassword-operator/pkg/onepassword/client"
|
||||
"github.com/1Password/onepassword-operator/pkg/utils"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
@@ -45,6 +46,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
)
|
||||
|
||||
var logDeployment = logf.Log.WithName("controller_deployment")
|
||||
@@ -53,7 +55,7 @@ var logDeployment = logf.Log.WithName("controller_deployment")
|
||||
type DeploymentReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
OpConnectClient connect.Client
|
||||
OpClient opclient.Client
|
||||
OpAnnotationRegExp *regexp.Regexp
|
||||
}
|
||||
|
||||
@@ -72,10 +74,10 @@ type DeploymentReconciler struct {
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/reconcile
|
||||
func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
reqLogger := logDeployment.WithValues("Request.Namespace", req.Namespace, "Request.Name", req.Name)
|
||||
reqLogger.Info("Reconciling Deployment")
|
||||
reqLogger.V(logs.DebugLevel).Info("Reconciling Deployment")
|
||||
|
||||
deployment := &appsv1.Deployment{}
|
||||
err := r.Get(context.Background(), req.NamespacedName, deployment)
|
||||
err := r.Get(ctx, req.NamespacedName, deployment)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return reconcile.Result{}, nil
|
||||
@@ -85,7 +87,7 @@ func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
||||
|
||||
annotations, annotationsFound := op.GetAnnotationsForDeployment(deployment, r.OpAnnotationRegExp)
|
||||
if !annotationsFound {
|
||||
reqLogger.Info("No 1Password Annotations found")
|
||||
reqLogger.V(logs.DebugLevel).Info("No 1Password Annotations found")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
@@ -95,13 +97,18 @@ func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
||||
// This is so we can handle cleanup of associated secrets properly
|
||||
if !utils.ContainsString(deployment.ObjectMeta.Finalizers, finalizer) {
|
||||
deployment.ObjectMeta.Finalizers = append(deployment.ObjectMeta.Finalizers, finalizer)
|
||||
if err := r.Update(context.Background(), deployment); err != nil {
|
||||
if err = r.Update(ctx, deployment); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
}
|
||||
// Handles creation or updating secrets for deployment if needed
|
||||
if err := r.handleApplyingDeployment(deployment, deployment.Namespace, annotations, req); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
if err = r.handleApplyingDeployment(ctx, deployment, deployment.Namespace, annotations, req); err != nil {
|
||||
if strings.Contains(err.Error(), "rate limit") {
|
||||
reqLogger.V(logs.InfoLevel).Info("1Password rate limit hit. Requeuing after 15 minutes.")
|
||||
return ctrl.Result{RequeueAfter: 15 * time.Minute}, nil
|
||||
} else {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
@@ -110,10 +117,12 @@ func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
||||
if utils.ContainsString(deployment.ObjectMeta.Finalizers, finalizer) {
|
||||
|
||||
secretName := annotations[op.NameAnnotation]
|
||||
r.cleanupKubernetesSecretForDeployment(secretName, deployment)
|
||||
if err = r.cleanupKubernetesSecretForDeployment(ctx, secretName, deployment); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Remove the finalizer from the deployment so deletion of deployment can be completed
|
||||
if err := r.removeOnePasswordFinalizerFromDeployment(deployment); err != nil {
|
||||
if err = r.removeOnePasswordFinalizerFromDeployment(ctx, deployment); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
}
|
||||
@@ -127,7 +136,7 @@ func (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *DeploymentReconciler) cleanupKubernetesSecretForDeployment(secretName string, deletedDeployment *appsv1.Deployment) error {
|
||||
func (r *DeploymentReconciler) cleanupKubernetesSecretForDeployment(ctx context.Context, secretName string, deletedDeployment *appsv1.Deployment) error {
|
||||
kubernetesSecret := &corev1.Secret{}
|
||||
kubernetesSecret.ObjectMeta.Name = secretName
|
||||
kubernetesSecret.ObjectMeta.Namespace = deletedDeployment.Namespace
|
||||
@@ -137,14 +146,14 @@ func (r *DeploymentReconciler) cleanupKubernetesSecretForDeployment(secretName s
|
||||
}
|
||||
updatedSecrets := map[string]*corev1.Secret{secretName: kubernetesSecret}
|
||||
|
||||
multipleDeploymentsUsingSecret, err := r.areMultipleDeploymentsUsingSecret(updatedSecrets, *deletedDeployment)
|
||||
multipleDeploymentsUsingSecret, err := r.areMultipleDeploymentsUsingSecret(ctx, updatedSecrets, *deletedDeployment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only delete the associated kubernetes secret if it is not being used by other deployments
|
||||
if !multipleDeploymentsUsingSecret {
|
||||
if err := r.Delete(context.Background(), kubernetesSecret); err != nil {
|
||||
if err = r.Delete(ctx, kubernetesSecret); err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
@@ -153,13 +162,13 @@ func (r *DeploymentReconciler) cleanupKubernetesSecretForDeployment(secretName s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DeploymentReconciler) areMultipleDeploymentsUsingSecret(updatedSecrets map[string]*corev1.Secret, deletedDeployment appsv1.Deployment) (bool, error) {
|
||||
func (r *DeploymentReconciler) areMultipleDeploymentsUsingSecret(ctx context.Context, updatedSecrets map[string]*corev1.Secret, deletedDeployment appsv1.Deployment) (bool, error) {
|
||||
deployments := &appsv1.DeploymentList{}
|
||||
opts := []client.ListOption{
|
||||
client.InNamespace(deletedDeployment.Namespace),
|
||||
}
|
||||
|
||||
err := r.List(context.Background(), deployments, opts...)
|
||||
err := r.List(ctx, deployments, opts...)
|
||||
if err != nil {
|
||||
logDeployment.Error(err, "Failed to list kubernetes deployments")
|
||||
return false, err
|
||||
@@ -175,12 +184,12 @@ func (r *DeploymentReconciler) areMultipleDeploymentsUsingSecret(updatedSecrets
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *DeploymentReconciler) removeOnePasswordFinalizerFromDeployment(deployment *appsv1.Deployment) error {
|
||||
func (r *DeploymentReconciler) removeOnePasswordFinalizerFromDeployment(ctx context.Context, deployment *appsv1.Deployment) error {
|
||||
deployment.ObjectMeta.Finalizers = utils.RemoveString(deployment.ObjectMeta.Finalizers, finalizer)
|
||||
return r.Update(context.Background(), deployment)
|
||||
return r.Update(ctx, deployment)
|
||||
}
|
||||
|
||||
func (r *DeploymentReconciler) handleApplyingDeployment(deployment *appsv1.Deployment, namespace string, annotations map[string]string, request reconcile.Request) error {
|
||||
func (r *DeploymentReconciler) handleApplyingDeployment(ctx context.Context, deployment *appsv1.Deployment, namespace string, annotations map[string]string, request reconcile.Request) error {
|
||||
reqLog := logDeployment.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
|
||||
|
||||
secretName := annotations[op.NameAnnotation]
|
||||
@@ -192,7 +201,7 @@ func (r *DeploymentReconciler) handleApplyingDeployment(deployment *appsv1.Deplo
|
||||
return nil
|
||||
}
|
||||
|
||||
item, err := op.GetOnePasswordItemByPath(r.OpConnectClient, annotations[op.ItemPathAnnotation])
|
||||
item, err := op.GetOnePasswordItemByPath(ctx, r.OpClient, annotations[op.ItemPathAnnotation])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to retrieve item: %v", err)
|
||||
}
|
||||
@@ -209,5 +218,5 @@ func (r *DeploymentReconciler) handleApplyingDeployment(deployment *appsv1.Deplo
|
||||
UID: deployment.GetUID(),
|
||||
}
|
||||
|
||||
return kubeSecrets.CreateKubernetesSecretFromItem(r.Client, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, ownerRef)
|
||||
return kubeSecrets.CreateKubernetesSecretFromItem(ctx, r.Client, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, ownerRef)
|
||||
}
|
@@ -1,10 +1,7 @@
|
||||
package controllers
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
"github.com/1Password/onepassword-operator/pkg/mocks"
|
||||
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
@@ -17,6 +14,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/api/v1"
|
||||
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -26,14 +24,13 @@ const (
|
||||
)
|
||||
|
||||
var _ = Describe("Deployment controller", func() {
|
||||
var ctx context.Context
|
||||
ctx := context.Background()
|
||||
var deploymentKey types.NamespacedName
|
||||
var secretKey types.NamespacedName
|
||||
var deploymentResource *appsv1.Deployment
|
||||
createdSecret := &v1.Secret{}
|
||||
|
||||
makeDeployment := func() {
|
||||
ctx = context.Background()
|
||||
|
||||
deploymentKey = types.NamespacedName{
|
||||
Name: deploymentName,
|
||||
@@ -95,28 +92,19 @@ var _ = Describe("Deployment controller", func() {
|
||||
|
||||
cleanK8sResources := func() {
|
||||
// failed test runs that don't clean up leave resources behind.
|
||||
err := k8sClient.DeleteAllOf(context.Background(), &onepasswordv1.OnePasswordItem{}, client.InNamespace(namespace))
|
||||
err := k8sClient.DeleteAllOf(ctx, &onepasswordv1.OnePasswordItem{}, client.InNamespace(namespace))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = k8sClient.DeleteAllOf(context.Background(), &v1.Secret{}, client.InNamespace(namespace))
|
||||
err = k8sClient.DeleteAllOf(ctx, &v1.Secret{}, client.InNamespace(namespace))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = k8sClient.DeleteAllOf(context.Background(), &appsv1.Deployment{}, client.InNamespace(namespace))
|
||||
err = k8sClient.DeleteAllOf(ctx, &appsv1.Deployment{}, client.InNamespace(namespace))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
mockGetItemFunc := func() {
|
||||
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
item := onepassword.Item{}
|
||||
item.Fields = []*onepassword.ItemField{}
|
||||
for k, v := range item1.Data {
|
||||
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||
}
|
||||
item.Version = item1.Version
|
||||
item.Vault.ID = vaultUUID
|
||||
item.ID = uuid
|
||||
return &item, nil
|
||||
}
|
||||
// mock GetItemByID to return test item 'item1'
|
||||
mockGetItemByIDFunc.Return(item1.ToModel(), nil)
|
||||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
@@ -151,17 +139,10 @@ var _ = Describe("Deployment controller", func() {
|
||||
|
||||
It("Should update existing K8s Secret using deployment", func() {
|
||||
By("Updating secret")
|
||||
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
item := onepassword.Item{}
|
||||
item.Fields = []*onepassword.ItemField{}
|
||||
for k, v := range item2.Data {
|
||||
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||
}
|
||||
item.Version = item2.Version
|
||||
item.Vault.ID = vaultUUID
|
||||
item.ID = uuid
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
// mock GetItemByID to return test item 'item2'
|
||||
mockGetItemByIDFunc.Return(item2.ToModel(), nil)
|
||||
|
||||
Eventually(func() error {
|
||||
updatedDeployment := &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 1Password
|
||||
Copyright (c) 2020-2024 1Password
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,17 +22,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/api/v1"
|
||||
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
|
||||
"github.com/1Password/onepassword-operator/pkg/logs"
|
||||
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||
opclient "github.com/1Password/onepassword-operator/pkg/onepassword/client"
|
||||
"github.com/1Password/onepassword-operator/pkg/utils"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -51,8 +53,8 @@ var finalizer = "onepassword.com/finalizer.secret"
|
||||
// OnePasswordItemReconciler reconciles a OnePasswordItem object
|
||||
type OnePasswordItemReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
OpConnectClient connect.Client
|
||||
Scheme *runtime.Scheme
|
||||
OpClient opclient.Client
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=onepassword.com,resources=onepassworditems,verbs=get;list;watch;create;update;patch;delete
|
||||
@@ -66,6 +68,7 @@ type OnePasswordItemReconciler struct {
|
||||
//+kubebuilder:rbac:groups=apps,resourceNames=onepassword-connect-operator,resources=deployments/finalizers,verbs=update
|
||||
//+kubebuilder:rbac:groups=onepassword.com,resources=*,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create
|
||||
//+kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;create;update
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||
// move the current state of the cluster closer to the desired state.
|
||||
@@ -78,10 +81,10 @@ type OnePasswordItemReconciler struct {
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/reconcile
|
||||
func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
reqLogger := logOnePasswordItem.WithValues("Request.Namespace", req.Namespace, "Request.Name", req.Name)
|
||||
reqLogger.Info("Reconciling OnePasswordItem")
|
||||
reqLogger.V(logs.DebugLevel).Info("Reconciling OnePasswordItem")
|
||||
|
||||
onepassworditem := &onepasswordv1.OnePasswordItem{}
|
||||
err := r.Get(context.Background(), req.NamespacedName, onepassworditem)
|
||||
err := r.Get(ctx, req.NamespacedName, onepassworditem)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return ctrl.Result{}, nil
|
||||
@@ -95,14 +98,20 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||
// This is so we can handle cleanup of associated secrets properly
|
||||
if !utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) {
|
||||
onepassworditem.ObjectMeta.Finalizers = append(onepassworditem.ObjectMeta.Finalizers, finalizer)
|
||||
if err := r.Update(context.Background(), onepassworditem); err != nil {
|
||||
if err = r.Update(ctx, onepassworditem); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Handles creation or updating secrets for deployment if needed
|
||||
err := r.handleOnePasswordItem(onepassworditem, req)
|
||||
if updateStatusErr := r.updateStatus(onepassworditem, err); updateStatusErr != nil {
|
||||
err = r.handleOnePasswordItem(ctx, onepassworditem, req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "rate limit") {
|
||||
reqLogger.V(logs.InfoLevel).Info("1Password rate limit hit. Requeuing after 15 minutes.")
|
||||
return ctrl.Result{RequeueAfter: 15 * time.Minute}, nil
|
||||
}
|
||||
}
|
||||
if updateStatusErr := r.updateStatus(ctx, onepassworditem, err); updateStatusErr != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("cannot update status: %s", updateStatusErr)
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
@@ -111,12 +120,12 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||
if utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) {
|
||||
|
||||
// Delete associated kubernetes secret
|
||||
if err = r.cleanupKubernetesSecret(onepassworditem); err != nil {
|
||||
if err = r.cleanupKubernetesSecret(ctx, onepassworditem); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Remove finalizer now that cleanup is complete
|
||||
if err := r.removeFinalizer(onepassworditem); err != nil {
|
||||
if err = r.removeFinalizer(ctx, onepassworditem); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
@@ -130,21 +139,20 @@ func (r *OnePasswordItemReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *OnePasswordItemReconciler) removeFinalizer(onePasswordItem *onepasswordv1.OnePasswordItem) error {
|
||||
func (r *OnePasswordItemReconciler) removeFinalizer(ctx context.Context, onePasswordItem *onepasswordv1.OnePasswordItem) error {
|
||||
onePasswordItem.ObjectMeta.Finalizers = utils.RemoveString(onePasswordItem.ObjectMeta.Finalizers, finalizer)
|
||||
if err := r.Update(context.Background(), onePasswordItem); err != nil {
|
||||
if err := r.Update(ctx, onePasswordItem); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *OnePasswordItemReconciler) cleanupKubernetesSecret(onePasswordItem *onepasswordv1.OnePasswordItem) error {
|
||||
func (r *OnePasswordItemReconciler) cleanupKubernetesSecret(ctx context.Context, onePasswordItem *onepasswordv1.OnePasswordItem) error {
|
||||
kubernetesSecret := &corev1.Secret{}
|
||||
kubernetesSecret.ObjectMeta.Name = onePasswordItem.Name
|
||||
kubernetesSecret.ObjectMeta.Namespace = onePasswordItem.Namespace
|
||||
|
||||
r.Delete(context.Background(), kubernetesSecret)
|
||||
if err := r.Delete(context.Background(), kubernetesSecret); err != nil {
|
||||
if err := r.Delete(ctx, kubernetesSecret); err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
@@ -152,18 +160,18 @@ func (r *OnePasswordItemReconciler) cleanupKubernetesSecret(onePasswordItem *one
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *OnePasswordItemReconciler) removeOnePasswordFinalizerFromOnePasswordItem(opSecret *onepasswordv1.OnePasswordItem) error {
|
||||
func (r *OnePasswordItemReconciler) removeOnePasswordFinalizerFromOnePasswordItem(ctx context.Context, opSecret *onepasswordv1.OnePasswordItem) error {
|
||||
opSecret.ObjectMeta.Finalizers = utils.RemoveString(opSecret.ObjectMeta.Finalizers, finalizer)
|
||||
return r.Update(context.Background(), opSecret)
|
||||
return r.Update(ctx, opSecret)
|
||||
}
|
||||
|
||||
func (r *OnePasswordItemReconciler) handleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, req ctrl.Request) error {
|
||||
func (r *OnePasswordItemReconciler) handleOnePasswordItem(ctx context.Context, resource *onepasswordv1.OnePasswordItem, req ctrl.Request) error {
|
||||
secretName := resource.GetName()
|
||||
labels := resource.Labels
|
||||
secretType := resource.Type
|
||||
autoRestart := resource.Annotations[op.RestartDeploymentsAnnotation]
|
||||
|
||||
item, err := op.GetOnePasswordItemByPath(r.OpConnectClient, resource.Spec.ItemPath)
|
||||
item, err := op.GetOnePasswordItemByPath(ctx, r.OpClient, resource.Spec.ItemPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to retrieve item: %v", err)
|
||||
}
|
||||
@@ -180,10 +188,10 @@ func (r *OnePasswordItemReconciler) handleOnePasswordItem(resource *onepasswordv
|
||||
UID: resource.GetUID(),
|
||||
}
|
||||
|
||||
return kubeSecrets.CreateKubernetesSecretFromItem(r.Client, secretName, resource.Namespace, item, autoRestart, labels, secretType, ownerRef)
|
||||
return kubeSecrets.CreateKubernetesSecretFromItem(ctx, r.Client, secretName, resource.Namespace, item, autoRestart, labels, secretType, ownerRef)
|
||||
}
|
||||
|
||||
func (r *OnePasswordItemReconciler) updateStatus(resource *onepasswordv1.OnePasswordItem, err error) error {
|
||||
func (r *OnePasswordItemReconciler) updateStatus(ctx context.Context, resource *onepasswordv1.OnePasswordItem, err error) error {
|
||||
existingCondition := findCondition(resource.Status.Conditions, onepasswordv1.OnePasswordItemReady)
|
||||
updatedCondition := existingCondition
|
||||
if err != nil {
|
||||
@@ -199,7 +207,7 @@ func (r *OnePasswordItemReconciler) updateStatus(resource *onepasswordv1.OnePass
|
||||
}
|
||||
|
||||
resource.Status.Conditions = []onepasswordv1.OnePasswordItemCondition{updatedCondition}
|
||||
return r.Status().Update(context.Background(), resource)
|
||||
return r.Status().Update(ctx, resource)
|
||||
}
|
||||
|
||||
func findCondition(conditions []onepasswordv1.OnePasswordItemCondition, t onepasswordv1.OnePasswordItemConditionType) onepasswordv1.OnePasswordItemCondition {
|
@@ -1,11 +1,7 @@
|
||||
package controllers
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
"github.com/1Password/onepassword-operator/pkg/mocks"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
@@ -16,6 +12,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/api/v1"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,17 +29,8 @@ var _ = Describe("OnePasswordItem controller", func() {
|
||||
err = k8sClient.DeleteAllOf(context.Background(), &v1.Secret{}, client.InNamespace(namespace))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
item := onepassword.Item{}
|
||||
item.Fields = []*onepassword.ItemField{}
|
||||
for k, v := range item1.Data {
|
||||
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||
}
|
||||
item.Version = item1.Version
|
||||
item.Vault.ID = vaultUUID
|
||||
item.ID = uuid
|
||||
return &item, nil
|
||||
}
|
||||
item := item1.ToModel()
|
||||
mockGetItemByIDFunc.Return(item, nil)
|
||||
})
|
||||
|
||||
Context("Happy path", func() {
|
||||
@@ -99,17 +87,13 @@ var _ = Describe("OnePasswordItem controller", func() {
|
||||
"password": []byte("##newPassword##"),
|
||||
"extraField": []byte("dev"),
|
||||
}
|
||||
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
item := onepassword.Item{}
|
||||
item.Fields = []*onepassword.ItemField{}
|
||||
for k, v := range newData {
|
||||
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||
}
|
||||
item.Version = item1.Version + 1
|
||||
item.Vault.ID = vaultUUID
|
||||
item.ID = uuid
|
||||
return &item, nil
|
||||
|
||||
item := item2.ToModel()
|
||||
for k, v := range newData {
|
||||
item.Fields = append(item.Fields, model.ItemField{Label: k, Value: v})
|
||||
}
|
||||
mockGetItemByIDFunc.Return(item, nil)
|
||||
|
||||
_, err := onePasswordItemReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: key})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
@@ -178,18 +162,11 @@ var _ = Describe("OnePasswordItem controller", func() {
|
||||
"ice-cream-type": []byte(iceCream),
|
||||
}
|
||||
|
||||
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||
item := onepassword.Item{}
|
||||
item.Title = "!my sECReT it3m%"
|
||||
item.Fields = []*onepassword.ItemField{}
|
||||
for k, v := range testData {
|
||||
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||
}
|
||||
item.Version = item1.Version + 1
|
||||
item.Vault.ID = vaultUUID
|
||||
item.ID = uuid
|
||||
return &item, nil
|
||||
item := item2.ToModel()
|
||||
for k, v := range testData {
|
||||
item.Fields = append(item.Fields, model.ItemField{Label: k, Value: v})
|
||||
}
|
||||
mockGetItemByIDFunc.Return(item, nil)
|
||||
|
||||
By("Creating a new OnePasswordItem successfully")
|
||||
Expect(k8sClient.Create(ctx, toCreate)).Should(Succeed())
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 1Password
|
||||
Copyright (c) 2020-2024 1Password
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -31,10 +31,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/mocks"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
@@ -45,6 +44,8 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
onepasswordcomv1 "github.com/1Password/onepassword-operator/api/v1"
|
||||
"github.com/1Password/onepassword-operator/pkg/mocks"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
//+kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
@@ -78,8 +79,11 @@ var (
|
||||
cancel context.CancelFunc
|
||||
onePasswordItemReconciler *OnePasswordItemReconciler
|
||||
deploymentReconciler *DeploymentReconciler
|
||||
mockGetItemByIDFunc *mock.Call
|
||||
|
||||
item1 = &TestItem{
|
||||
ItemID: "nwrhuano7bcwddcviubpp4mhfq",
|
||||
VaultID: "hfnjvi6aymbsnfc2xeeoheizda",
|
||||
Name: "test-item",
|
||||
Version: 123,
|
||||
Path: "vaults/hfnjvi6aymbsnfc2xeeoheizda/items/nwrhuano7bcwddcviubpp4mhfq",
|
||||
@@ -94,6 +98,8 @@ var (
|
||||
}
|
||||
|
||||
item2 = &TestItem{
|
||||
ItemID: "nwrhuano7bcwddcviubpp4mhf2",
|
||||
VaultID: "hfnjvi6aymbsnfc2xeeoheizd2",
|
||||
Name: "test-item2",
|
||||
Path: "vaults/hfnjvi6aymbsnfc2xeeoheizd2/items/nwrhuano7bcwddcviubpp4mhf2",
|
||||
Version: 456,
|
||||
@@ -109,6 +115,8 @@ var (
|
||||
)
|
||||
|
||||
type TestItem struct {
|
||||
ItemID string
|
||||
VaultID string
|
||||
Name string
|
||||
Version int
|
||||
Path string
|
||||
@@ -116,6 +124,20 @@ type TestItem struct {
|
||||
SecretData map[string][]byte
|
||||
}
|
||||
|
||||
func (ti *TestItem) ToModel() *model.Item {
|
||||
item := &model.Item{}
|
||||
item.Version = ti.Version
|
||||
item.VaultID = ti.VaultID
|
||||
item.ID = ti.ItemID
|
||||
|
||||
item.Fields = []model.ItemField{}
|
||||
for k, v := range ti.Data {
|
||||
item.Fields = append(item.Fields, model.ItemField{Label: k, Value: v})
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
@@ -129,7 +151,7 @@ var _ = BeforeSuite(func() {
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
|
||||
ErrorIfCRDPathMissing: true,
|
||||
}
|
||||
|
||||
@@ -153,12 +175,13 @@ var _ = BeforeSuite(func() {
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
opConnectClient := &mocks.TestClient{}
|
||||
mockOpClient := &mocks.TestClient{}
|
||||
mockGetItemByIDFunc = mockOpClient.On("GetItemByID", mock.Anything, mock.Anything)
|
||||
|
||||
onePasswordItemReconciler = &OnePasswordItemReconciler{
|
||||
Client: k8sManager.GetClient(),
|
||||
Scheme: k8sManager.GetScheme(),
|
||||
OpConnectClient: opConnectClient,
|
||||
Client: k8sManager.GetClient(),
|
||||
Scheme: k8sManager.GetScheme(),
|
||||
OpClient: mockOpClient,
|
||||
}
|
||||
err = (onePasswordItemReconciler).SetupWithManager(k8sManager)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@@ -167,7 +190,7 @@ var _ = BeforeSuite(func() {
|
||||
deploymentReconciler = &DeploymentReconciler{
|
||||
Client: k8sManager.GetClient(),
|
||||
Scheme: k8sManager.GetScheme(),
|
||||
OpConnectClient: opConnectClient,
|
||||
OpClient: mockOpClient,
|
||||
OpAnnotationRegExp: r,
|
||||
}
|
||||
err = (deploymentReconciler).SetupWithManager(k8sManager)
|
@@ -2,17 +2,13 @@ package kubernetessecrets
|
||||
|
||||
import (
|
||||
"context"
|
||||
errs "errors"
|
||||
"fmt"
|
||||
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"reflect"
|
||||
|
||||
errs "errors"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
"github.com/1Password/onepassword-operator/pkg/utils"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -34,11 +30,11 @@ var ErrCannotUpdateSecretType = errs.New("Cannot change secret type. Secret type
|
||||
|
||||
var log = logf.Log
|
||||
|
||||
func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]string, secretType string, ownerRef *metav1.OwnerReference) error {
|
||||
func CreateKubernetesSecretFromItem(ctx context.Context, kubeClient kubernetesClient.Client, secretName, namespace string, item *model.Item, autoRestart string, labels map[string]string, secretType string, ownerRef *metav1.OwnerReference) error {
|
||||
itemVersion := fmt.Sprint(item.Version)
|
||||
secretAnnotations := map[string]string{
|
||||
VersionAnnotation: itemVersion,
|
||||
ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID),
|
||||
ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.VaultID, item.ID),
|
||||
}
|
||||
|
||||
if autoRestart != "" {
|
||||
@@ -53,10 +49,10 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa
|
||||
secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item, ownerRef)
|
||||
|
||||
currentSecret := &corev1.Secret{}
|
||||
err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret)
|
||||
err := kubeClient.Get(ctx, 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)
|
||||
return kubeClient.Create(ctx, secret)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -82,7 +78,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa
|
||||
currentSecret.ObjectMeta.Annotations = secretAnnotations
|
||||
currentSecret.ObjectMeta.Labels = labels
|
||||
currentSecret.Data = secret.Data
|
||||
if err := kubeClient.Update(context.Background(), currentSecret); err != nil {
|
||||
if err := kubeClient.Update(ctx, currentSecret); err != nil {
|
||||
return fmt.Errorf("Kubernetes secret update failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -92,7 +88,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, secretType string, item onepassword.Item, ownerRef *metav1.OwnerReference) *corev1.Secret {
|
||||
func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, secretType string, item model.Item, ownerRef *metav1.OwnerReference) *corev1.Secret {
|
||||
var ownerRefs []metav1.OwnerReference
|
||||
if ownerRef != nil {
|
||||
ownerRefs = []metav1.OwnerReference{*ownerRef}
|
||||
@@ -111,7 +107,7 @@ func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotation
|
||||
}
|
||||
}
|
||||
|
||||
func BuildKubernetesSecretData(fields []*onepassword.ItemField, files []*onepassword.File) map[string][]byte {
|
||||
func BuildKubernetesSecretData(fields []model.ItemField, files []model.File) map[string][]byte {
|
||||
secretData := map[string][]byte{}
|
||||
for i := 0; i < len(fields); i++ {
|
||||
if fields[i].Value != "" {
|
||||
|
@@ -3,11 +3,10 @@ package kubernetessecrets
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -18,25 +17,26 @@ import (
|
||||
const restartDeploymentAnnotation = "false"
|
||||
|
||||
func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
secretName := "test-secret-name"
|
||||
namespace := "test"
|
||||
|
||||
item := onepassword.Item{}
|
||||
item := model.Item{}
|
||||
item.Fields = generateFields(5)
|
||||
item.Version = 123
|
||||
item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
item.VaultID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
item.ID = "h46bb3jddvay7nxopfhvlwg35q"
|
||||
|
||||
kubeClient := fake.NewClientBuilder().Build()
|
||||
secretLabels := map[string]string{}
|
||||
secretType := ""
|
||||
|
||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||
err := CreateKubernetesSecretFromItem(ctx, kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
createdSecret := &corev1.Secret{}
|
||||
err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret)
|
||||
err = kubeClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Secret was not created: %v", err)
|
||||
@@ -46,13 +46,14 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
secretName := "test-secret-name"
|
||||
namespace := "test"
|
||||
|
||||
item := onepassword.Item{}
|
||||
item := model.Item{}
|
||||
item.Fields = generateFields(5)
|
||||
item.Version = 123
|
||||
item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
item.VaultID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
item.ID = "h46bb3jddvay7nxopfhvlwg35q"
|
||||
|
||||
kubeClient := fake.NewClientBuilder().Build()
|
||||
@@ -65,12 +66,12 @@ func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) {
|
||||
Name: "test-deployment",
|
||||
UID: types.UID("test-uid"),
|
||||
}
|
||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, ownerRef)
|
||||
err := CreateKubernetesSecretFromItem(ctx, kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, ownerRef)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
createdSecret := &corev1.Secret{}
|
||||
err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret)
|
||||
err = kubeClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret)
|
||||
|
||||
// Check owner references.
|
||||
gotOwnerRefs := createdSecret.ObjectMeta.OwnerReferences
|
||||
@@ -91,37 +92,38 @@ func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
secretName := "test-secret-update"
|
||||
namespace := "test"
|
||||
|
||||
item := onepassword.Item{}
|
||||
item := model.Item{}
|
||||
item.Fields = generateFields(5)
|
||||
item.Version = 123
|
||||
item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
item.VaultID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
item.ID = "h46bb3jddvay7nxopfhvlwg35q"
|
||||
|
||||
kubeClient := fake.NewClientBuilder().Build()
|
||||
secretLabels := map[string]string{}
|
||||
secretType := ""
|
||||
|
||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||
err := CreateKubernetesSecretFromItem(ctx, kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Updating kubernetes secret with new item
|
||||
newItem := onepassword.Item{}
|
||||
newItem := model.Item{}
|
||||
newItem.Fields = generateFields(6)
|
||||
newItem.Version = 456
|
||||
newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
newItem.VaultID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
newItem.ID = "h46bb3jddvay7nxopfhvlwg35q"
|
||||
err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||
err = CreateKubernetesSecretFromItem(ctx, kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
updatedSecret := &corev1.Secret{}
|
||||
err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, updatedSecret)
|
||||
err = kubeClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, updatedSecret)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Secret was not found: %v", err)
|
||||
@@ -147,7 +149,7 @@ func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) {
|
||||
annotations := map[string]string{
|
||||
annotationKey: annotationValue,
|
||||
}
|
||||
item := onepassword.Item{}
|
||||
item := model.Item{}
|
||||
item.Fields = generateFields(5)
|
||||
labels := map[string]string{}
|
||||
secretType := ""
|
||||
@@ -173,10 +175,10 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) {
|
||||
"annotationKey": "annotationValue",
|
||||
}
|
||||
labels := map[string]string{}
|
||||
item := onepassword.Item{}
|
||||
item := model.Item{}
|
||||
secretType := ""
|
||||
|
||||
item.Fields = []*onepassword.ItemField{
|
||||
item.Fields = []model.ItemField{
|
||||
{
|
||||
Label: "label w%th invalid ch!rs-",
|
||||
Value: "value1",
|
||||
@@ -206,25 +208,26 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateKubernetesTLSSecretFromOnePasswordItem(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
secretName := "tls-test-secret-name"
|
||||
namespace := "test"
|
||||
|
||||
item := onepassword.Item{}
|
||||
item := model.Item{}
|
||||
item.Fields = generateFields(5)
|
||||
item.Version = 123
|
||||
item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
item.VaultID = "hfnjvi6aymbsnfc2xeeoheizda"
|
||||
item.ID = "h46bb3jddvay7nxopfhvlwg35q"
|
||||
|
||||
kubeClient := fake.NewClientBuilder().Build()
|
||||
secretLabels := map[string]string{}
|
||||
secretType := "kubernetes.io/tls"
|
||||
|
||||
err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||
err := CreateKubernetesSecretFromItem(ctx, kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
createdSecret := &corev1.Secret{}
|
||||
err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret)
|
||||
err = kubeClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Secret was not created: %v", err)
|
||||
@@ -235,13 +238,13 @@ func TestCreateKubernetesTLSSecretFromOnePasswordItem(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func compareAnnotationsToItem(annotations map[string]string, item onepassword.Item, t *testing.T) {
|
||||
func compareAnnotationsToItem(annotations map[string]string, item model.Item, t *testing.T) {
|
||||
actualVaultId, actualItemId, err := ParseVaultIdAndItemIdFromPath(annotations[ItemPathAnnotation])
|
||||
if err != nil {
|
||||
t.Errorf("Was unable to parse Item Path")
|
||||
}
|
||||
if actualVaultId != item.Vault.ID {
|
||||
t.Errorf("Expected annotation vault id to be %v but was %v", item.Vault.ID, actualVaultId)
|
||||
if actualVaultId != item.VaultID {
|
||||
t.Errorf("Expected annotation vault id to be %v but was %v", item.VaultID, actualVaultId)
|
||||
}
|
||||
if actualItemId != item.ID {
|
||||
t.Errorf("Expected annotation item id to be %v but was %v", item.ID, actualItemId)
|
||||
@@ -255,7 +258,7 @@ func compareAnnotationsToItem(annotations map[string]string, item onepassword.It
|
||||
}
|
||||
}
|
||||
|
||||
func compareFields(actualFields []*onepassword.ItemField, secretData map[string][]byte, t *testing.T) {
|
||||
func compareFields(actualFields []model.ItemField, secretData map[string][]byte, t *testing.T) {
|
||||
for i := 0; i < len(actualFields); i++ {
|
||||
value, found := secretData[actualFields[i].Label]
|
||||
if !found {
|
||||
@@ -267,14 +270,13 @@ func compareFields(actualFields []*onepassword.ItemField, secretData map[string]
|
||||
}
|
||||
}
|
||||
|
||||
func generateFields(numToGenerate int) []*onepassword.ItemField {
|
||||
fields := []*onepassword.ItemField{}
|
||||
func generateFields(numToGenerate int) []model.ItemField {
|
||||
fields := []model.ItemField{}
|
||||
for i := 0; i < numToGenerate; i++ {
|
||||
field := onepassword.ItemField{
|
||||
fields = append(fields, model.ItemField{
|
||||
Label: "key" + fmt.Sprint(i),
|
||||
Value: "value" + fmt.Sprint(i),
|
||||
}
|
||||
fields = append(fields, &field)
|
||||
})
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
11
pkg/logs/log_levels.go
Normal file
11
pkg/logs/log_levels.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package logs
|
||||
|
||||
// A Level is a logging priority. Lower levels are more important.
|
||||
// All levels have been multipled by -1 to ensure compatibilty
|
||||
// between zapcore and logr
|
||||
const (
|
||||
ErrorLevel = -2
|
||||
WarnLevel = -1
|
||||
InfoLevel = 0
|
||||
DebugLevel = 1
|
||||
)
|
@@ -1,151 +1,38 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
"context"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
)
|
||||
|
||||
type TestClient struct {
|
||||
GetVaultsFunc func() ([]onepassword.Vault, error)
|
||||
GetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error)
|
||||
GetVaultFunc func(uuid string) (*onepassword.Vault, error)
|
||||
GetVaultByUUIDFunc func(uuid string) (*onepassword.Vault, error)
|
||||
GetVaultByTitleFunc func(title string) (*onepassword.Vault, error)
|
||||
GetItemFunc func(itemQuery string, vaultQuery string) (*onepassword.Item, error)
|
||||
GetItemByUUIDFunc func(uuid string, vaultQuery string) (*onepassword.Item, error)
|
||||
GetItemByTitleFunc func(title string, vaultQuery string) (*onepassword.Item, error)
|
||||
GetItemsFunc func(vaultQuery string) ([]onepassword.Item, error)
|
||||
GetItemsByTitleFunc func(title string, vaultQuery string) ([]onepassword.Item, error)
|
||||
CreateItemFunc func(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error)
|
||||
UpdateItemFunc func(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error)
|
||||
DeleteItemFunc func(item *onepassword.Item, vaultQuery string) error
|
||||
DeleteItemByIDFunc func(itemUUID string, vaultQuery string) error
|
||||
DeleteItemByTitleFunc func(title string, vaultQuery string) error
|
||||
GetFilesFunc func(itemQuery string, vaultQuery string) ([]onepassword.File, error)
|
||||
GetFileFunc func(uuid string, itemQuery string, vaultQuery string) (*onepassword.File, error)
|
||||
GetFileContentFunc func(file *onepassword.File) ([]byte, error)
|
||||
DownloadFileFunc func(file *onepassword.File, targetDirectory string, overwrite bool) (string, error)
|
||||
LoadStructFromItemByUUIDFunc func(config interface{}, itemUUID string, vaultQuery string) error
|
||||
LoadStructFromItemByTitleFunc func(config interface{}, itemTitle string, vaultQuery string) error
|
||||
LoadStructFromItemFunc func(config interface{}, itemQuery string, vaultQuery string) error
|
||||
LoadStructFunc func(config interface{}) error
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
var (
|
||||
DoGetVaultsFunc func() ([]onepassword.Vault, error)
|
||||
DoGetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error)
|
||||
DoGetVaultFunc func(uuid string) (*onepassword.Vault, error)
|
||||
DoGetVaultByUUIDFunc func(uuid string) (*onepassword.Vault, error)
|
||||
DoGetVaultByTitleFunc func(title string) (*onepassword.Vault, error)
|
||||
DoGetItemFunc func(itemQuery string, vaultQuery string) (*onepassword.Item, error)
|
||||
DoGetItemByUUIDFunc func(uuid string, vaultQuery string) (*onepassword.Item, error)
|
||||
DoGetItemByTitleFunc func(title string, vaultQuery string) (*onepassword.Item, error)
|
||||
DoGetItemsFunc func(vaultQuery string) ([]onepassword.Item, error)
|
||||
DoGetItemsByTitleFunc func(title string, vaultQuery string) ([]onepassword.Item, error)
|
||||
DoCreateItemFunc func(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error)
|
||||
DoUpdateItemFunc func(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error)
|
||||
DoDeleteItemFunc func(item *onepassword.Item, vaultQuery string) error
|
||||
DoDeleteItemByIDFunc func(itemUUID string, vaultQuery string) error
|
||||
DoDeleteItemByTitleFunc func(title string, vaultQuery string) error
|
||||
DoGetFilesFunc func(itemQuery string, vaultQuery string) ([]onepassword.File, error)
|
||||
DoGetFileFunc func(uuid string, itemQuery string, vaultQuery string) (*onepassword.File, error)
|
||||
DoGetFileContentFunc func(file *onepassword.File) ([]byte, error)
|
||||
DoDownloadFileFunc func(file *onepassword.File, targetDirectory string, overwrite bool) (string, error)
|
||||
DoLoadStructFromItemByUUIDFunc func(config interface{}, itemUUID string, vaultQuery string) error
|
||||
DoLoadStructFromItemByTitleFunc func(config interface{}, itemTitle string, vaultQuery string) error
|
||||
DoLoadStructFromItemFunc func(config interface{}, itemQuery string, vaultQuery string) error
|
||||
DoLoadStructFunc func(config interface{}) error
|
||||
)
|
||||
|
||||
// Do is the mock client's `Do` func
|
||||
|
||||
func (m *TestClient) GetVaults() ([]onepassword.Vault, error) {
|
||||
return DoGetVaultsFunc()
|
||||
func (tc *TestClient) GetItemByID(ctx context.Context, vaultID, itemID string) (*model.Item, error) {
|
||||
args := tc.Called(vaultID, itemID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*model.Item), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) {
|
||||
return DoGetVaultsByTitleFunc(title)
|
||||
func (tc *TestClient) GetItemsByTitle(ctx context.Context, vaultID, itemTitle string) ([]model.Item, error) {
|
||||
args := tc.Called(vaultID, itemTitle)
|
||||
return args.Get(0).([]model.Item), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetVault(vaultQuery string) (*onepassword.Vault, error) {
|
||||
return DoGetVaultFunc(vaultQuery)
|
||||
func (tc *TestClient) GetFileContent(ctx context.Context, vaultID, itemID, fileID string) ([]byte, error) {
|
||||
args := tc.Called(vaultID, itemID, fileID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]byte), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetVaultByUUID(uuid string) (*onepassword.Vault, error) {
|
||||
return DoGetVaultByUUIDFunc(uuid)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetVaultByTitle(title string) (*onepassword.Vault, error) {
|
||||
return DoGetVaultByTitleFunc(title)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItem(itemQuery string, vaultQuery string) (*onepassword.Item, error) {
|
||||
return DoGetItemFunc(itemQuery, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItemByUUID(uuid string, vaultQuery string) (*onepassword.Item, error) {
|
||||
return DoGetItemByUUIDFunc(uuid, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItemByTitle(title string, vaultQuery string) (*onepassword.Item, error) {
|
||||
return DoGetItemByTitleFunc(title, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItems(vaultQuery string) ([]onepassword.Item, error) {
|
||||
return DoGetItemsFunc(vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetItemsByTitle(title string, vaultQuery string) ([]onepassword.Item, error) {
|
||||
return DoGetItemsByTitleFunc(title, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
|
||||
return DoCreateItemFunc(item, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) UpdateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
|
||||
return DoUpdateItemFunc(item, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) DeleteItem(item *onepassword.Item, vaultQuery string) error {
|
||||
return DoDeleteItemFunc(item, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) DeleteItemByID(itemUUID string, vaultQuery string) error {
|
||||
return DoDeleteItemByIDFunc(itemUUID, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) DeleteItemByTitle(title string, vaultQuery string) error {
|
||||
return DoDeleteItemByTitleFunc(title, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetFiles(itemQuery string, vaultQuery string) ([]onepassword.File, error) {
|
||||
return DoGetFilesFunc(itemQuery, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetFile(uuid string, itemQuery string, vaultQuery string) (*onepassword.File, error) {
|
||||
return DoGetFileFunc(uuid, itemQuery, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) GetFileContent(file *onepassword.File) ([]byte, error) {
|
||||
return DoGetFileContentFunc(file)
|
||||
}
|
||||
|
||||
func (m *TestClient) DownloadFile(file *onepassword.File, targetDirectory string, overwrite bool) (string, error) {
|
||||
return DoDownloadFileFunc(file, targetDirectory, overwrite)
|
||||
}
|
||||
|
||||
func (m *TestClient) LoadStructFromItemByUUID(config interface{}, itemUUID string, vaultQuery string) error {
|
||||
return DoLoadStructFromItemByUUIDFunc(config, itemUUID, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) LoadStructFromItemByTitle(config interface{}, itemTitle string, vaultQuery string) error {
|
||||
return DoLoadStructFromItemByTitleFunc(config, itemTitle, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) LoadStructFromItem(config interface{}, itemQuery string, vaultQuery string) error {
|
||||
return DoLoadStructFromItemFunc(config, itemQuery, vaultQuery)
|
||||
}
|
||||
|
||||
func (m *TestClient) LoadStruct(config interface{}) error {
|
||||
return DoLoadStructFunc(config)
|
||||
func (tc *TestClient) GetVaultsByTitle(ctx context.Context, title string) ([]model.Vault, error) {
|
||||
args := tc.Called(title)
|
||||
return args.Get(0).([]model.Vault), args.Error(1)
|
||||
}
|
||||
|
56
pkg/onepassword/client/client.go
Normal file
56
pkg/onepassword/client/client.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/client/connect"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/client/sdk"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
)
|
||||
|
||||
// Client is an interface for interacting with 1Password items and vaults.
|
||||
type Client interface {
|
||||
GetItemByID(ctx context.Context, vaultID, itemID string) (*model.Item, error)
|
||||
GetItemsByTitle(ctx context.Context, vaultID, itemTitle string) ([]model.Item, error)
|
||||
GetFileContent(ctx context.Context, vaultID, itemID, fileID string) ([]byte, error)
|
||||
GetVaultsByTitle(ctx context.Context, title string) ([]model.Vault, error)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Logger logr.Logger
|
||||
Version string
|
||||
}
|
||||
|
||||
// NewFromEnvironment creates a new 1Password client based on the provided configuration.
|
||||
func NewFromEnvironment(ctx context.Context, cfg Config) (Client, error) {
|
||||
connectHost, _ := os.LookupEnv("OP_CONNECT_HOST")
|
||||
connectToken, _ := os.LookupEnv("OP_CONNECT_TOKEN")
|
||||
serviceAccountToken, _ := os.LookupEnv("OP_SERVICE_ACCOUNT_TOKEN")
|
||||
|
||||
if connectHost != "" && connectToken != "" && serviceAccountToken != "" {
|
||||
return nil, errors.New("invalid configuration. Either Connect or Service Account credentials should be set, not both")
|
||||
}
|
||||
|
||||
if serviceAccountToken != "" {
|
||||
cfg.Logger.Info("Using Service Account Token")
|
||||
return sdk.NewClient(ctx, sdk.Config{
|
||||
ServiceAccountToken: serviceAccountToken,
|
||||
IntegrationName: "1password-operator",
|
||||
IntegrationVersion: cfg.Version,
|
||||
})
|
||||
}
|
||||
|
||||
if connectHost != "" && connectToken != "" {
|
||||
cfg.Logger.Info("Using 1Password Connect")
|
||||
return connect.NewClient(connect.Config{
|
||||
ConnectHost: connectHost,
|
||||
ConnectToken: connectToken,
|
||||
}), nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid configuration. Connect or Service Account credentials should be set")
|
||||
}
|
84
pkg/onepassword/client/connect/connect.go
Normal file
84
pkg/onepassword/client/connect/connect.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package connect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
)
|
||||
|
||||
// Config holds the configuration for the Connect client.
|
||||
type Config struct {
|
||||
ConnectHost string
|
||||
ConnectToken string
|
||||
}
|
||||
|
||||
// Connect is a client for interacting with 1Password using the Connect API.
|
||||
type Connect struct {
|
||||
client connect.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new Connect client using provided configuration.
|
||||
func NewClient(config Config) *Connect {
|
||||
return &Connect{
|
||||
client: connect.NewClient(config.ConnectHost, config.ConnectToken),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connect) GetItemByID(ctx context.Context, vaultID, itemID string) (*model.Item, error) {
|
||||
connectItem, err := c.client.GetItemByUUID(itemID, vaultID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("1Password Connect error: %w", err)
|
||||
}
|
||||
|
||||
var item model.Item
|
||||
item.FromConnectItem(connectItem)
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (c *Connect) GetItemsByTitle(ctx context.Context, vaultID, itemTitle string) ([]model.Item, error) {
|
||||
// Get all items in the vault with the specified title
|
||||
connectItems, err := c.client.GetItemsByTitle(itemTitle, vaultID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("1Password Connect error: %w", err)
|
||||
}
|
||||
|
||||
items := make([]model.Item, len(connectItems))
|
||||
for i, connectItem := range connectItems {
|
||||
var item model.Item
|
||||
item.FromConnectItem(&connectItem)
|
||||
items[i] = item
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (c *Connect) GetFileContent(ctx context.Context, vaultID, itemID, fileID string) ([]byte, error) {
|
||||
bytes, err := c.client.GetFileContent(&onepassword.File{
|
||||
ContentPath: fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s/content", vaultID, itemID, fileID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("1Password Connect error: %w", err)
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func (c *Connect) GetVaultsByTitle(ctx context.Context, vaultQuery string) ([]model.Vault, error) {
|
||||
connectVaults, err := c.client.GetVaultsByTitle(vaultQuery)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("1Password Connect error: %w", err)
|
||||
}
|
||||
|
||||
var vaults []model.Vault
|
||||
for _, connectVault := range connectVaults {
|
||||
if vaultQuery == connectVault.Name {
|
||||
var vault model.Vault
|
||||
vault.FromConnectVault(&connectVault)
|
||||
vaults = append(vaults, vault)
|
||||
}
|
||||
}
|
||||
return vaults, nil
|
||||
}
|
241
pkg/onepassword/client/connect/connect_test.go
Normal file
241
pkg/onepassword/client/connect/connect_test.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package connect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
clienttesting "github.com/1Password/onepassword-operator/pkg/onepassword/client/testing"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/client/testing/mock"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
)
|
||||
|
||||
const VaultTitleEmployee = "Employee"
|
||||
|
||||
func TestConnect_GetItemByID(t *testing.T) {
|
||||
connectItem := clienttesting.CreateConnectItem()
|
||||
|
||||
testCases := map[string]struct {
|
||||
mockClient func() *mock.ConnectClientMock
|
||||
check func(t *testing.T, item *model.Item, err error)
|
||||
}{
|
||||
"should return an item": {
|
||||
mockClient: func() *mock.ConnectClientMock {
|
||||
mockConnectClient := &mock.ConnectClientMock{}
|
||||
mockConnectClient.On("GetItemByUUID", "item-id", "vault-id").Return(connectItem, nil)
|
||||
return mockConnectClient
|
||||
},
|
||||
check: func(t *testing.T, item *model.Item, err error) {
|
||||
require.NoError(t, err)
|
||||
clienttesting.CheckConnectItemMapping(t, connectItem, item)
|
||||
},
|
||||
},
|
||||
"should return an error": {
|
||||
mockClient: func() *mock.ConnectClientMock {
|
||||
mockConnectClient := &mock.ConnectClientMock{}
|
||||
mockConnectClient.On("GetItemByUUID", "item-id", "vault-id").Return((*onepassword.Item)(nil), errors.New("error"))
|
||||
return mockConnectClient
|
||||
},
|
||||
check: func(t *testing.T, item *model.Item, err error) {
|
||||
require.Error(t, err)
|
||||
require.Nil(t, item)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for description, tc := range testCases {
|
||||
t.Run(description, func(t *testing.T) {
|
||||
client := &Connect{client: tc.mockClient()}
|
||||
item, err := client.GetItemByID(context.Background(), "vault-id", "item-id")
|
||||
tc.check(t, item, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnect_GetItemsByTitle(t *testing.T) {
|
||||
connectItem1 := clienttesting.CreateConnectItem()
|
||||
connectItem2 := clienttesting.CreateConnectItem()
|
||||
|
||||
testCases := map[string]struct {
|
||||
mockClient func() *mock.ConnectClientMock
|
||||
check func(t *testing.T, items []model.Item, err error)
|
||||
}{
|
||||
"should return a single item": {
|
||||
mockClient: func() *mock.ConnectClientMock {
|
||||
mockConnectClient := &mock.ConnectClientMock{}
|
||||
mockConnectClient.On("GetItemsByTitle", "item-title", "vault-id").Return(
|
||||
[]onepassword.Item{
|
||||
*connectItem1,
|
||||
}, nil)
|
||||
return mockConnectClient
|
||||
},
|
||||
check: func(t *testing.T, items []model.Item, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, items, 1)
|
||||
require.Equal(t, connectItem1.ID, items[0].ID)
|
||||
},
|
||||
},
|
||||
"should return two items": {
|
||||
mockClient: func() *mock.ConnectClientMock {
|
||||
mockConnectClient := &mock.ConnectClientMock{}
|
||||
mockConnectClient.On("GetItemsByTitle", "item-title", "vault-id").Return(
|
||||
[]onepassword.Item{
|
||||
*connectItem1,
|
||||
*connectItem2,
|
||||
}, nil)
|
||||
return mockConnectClient
|
||||
},
|
||||
check: func(t *testing.T, items []model.Item, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, items, 2)
|
||||
clienttesting.CheckConnectItemMapping(t, connectItem1, &items[0])
|
||||
clienttesting.CheckConnectItemMapping(t, connectItem2, &items[1])
|
||||
},
|
||||
},
|
||||
"should return an error": {
|
||||
mockClient: func() *mock.ConnectClientMock {
|
||||
mockConnectClient := &mock.ConnectClientMock{}
|
||||
mockConnectClient.On("GetItemsByTitle", "item-title", "vault-id").Return([]onepassword.Item{}, errors.New("error"))
|
||||
return mockConnectClient
|
||||
},
|
||||
check: func(t *testing.T, items []model.Item, err error) {
|
||||
require.Error(t, err)
|
||||
require.Nil(t, items)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for description, tc := range testCases {
|
||||
t.Run(description, func(t *testing.T) {
|
||||
client := &Connect{client: tc.mockClient()}
|
||||
items, err := client.GetItemsByTitle(context.Background(), "vault-id", "item-title")
|
||||
tc.check(t, items, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnect_GetFileContent(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
mockClient func() *mock.ConnectClientMock
|
||||
check func(t *testing.T, content []byte, err error)
|
||||
}{
|
||||
"should return file content": {
|
||||
mockClient: func() *mock.ConnectClientMock {
|
||||
mockConnectClient := &mock.ConnectClientMock{}
|
||||
mockConnectClient.On("GetFileContent", &onepassword.File{
|
||||
ContentPath: "/v1/vaults/vault-id/items/item-id/files/file-id/content",
|
||||
}).Return([]byte("file content"), nil)
|
||||
return mockConnectClient
|
||||
},
|
||||
check: func(t *testing.T, content []byte, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("file content"), content)
|
||||
},
|
||||
},
|
||||
"should return an error": {
|
||||
mockClient: func() *mock.ConnectClientMock {
|
||||
mockConnectClient := &mock.ConnectClientMock{}
|
||||
mockConnectClient.On("GetFileContent", &onepassword.File{
|
||||
ContentPath: "/v1/vaults/vault-id/items/item-id/files/file-id/content",
|
||||
}).Return(nil, errors.New("error"))
|
||||
return mockConnectClient
|
||||
},
|
||||
check: func(t *testing.T, content []byte, err error) {
|
||||
require.Error(t, err)
|
||||
require.Nil(t, content)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for description, tc := range testCases {
|
||||
t.Run(description, func(t *testing.T) {
|
||||
client := &Connect{client: tc.mockClient()}
|
||||
content, err := client.GetFileContent(context.Background(), "vault-id", "item-id", "file-id")
|
||||
tc.check(t, content, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnect_GetVaultsByTitle(t *testing.T) {
|
||||
now := time.Now()
|
||||
testCases := map[string]struct {
|
||||
mockClient func() *mock.ConnectClientMock
|
||||
check func(t *testing.T, vaults []model.Vault, err error)
|
||||
}{
|
||||
"should return a single vault": {
|
||||
mockClient: func() *mock.ConnectClientMock {
|
||||
mockConnectClient := &mock.ConnectClientMock{}
|
||||
mockConnectClient.On("GetVaultsByTitle", VaultTitleEmployee).Return([]onepassword.Vault{
|
||||
{
|
||||
ID: "test-id",
|
||||
Name: VaultTitleEmployee,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
ID: "test-id-2",
|
||||
Name: "Some other vault",
|
||||
CreatedAt: now,
|
||||
},
|
||||
}, nil)
|
||||
return mockConnectClient
|
||||
},
|
||||
check: func(t *testing.T, vaults []model.Vault, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, vaults, 1)
|
||||
require.Equal(t, "test-id", vaults[0].ID)
|
||||
require.Equal(t, now, vaults[0].CreatedAt)
|
||||
},
|
||||
},
|
||||
"should return a two vaults": {
|
||||
mockClient: func() *mock.ConnectClientMock {
|
||||
mockConnectClient := &mock.ConnectClientMock{}
|
||||
mockConnectClient.On("GetVaultsByTitle", VaultTitleEmployee).Return([]onepassword.Vault{
|
||||
{
|
||||
ID: "test-id",
|
||||
Name: VaultTitleEmployee,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
ID: "test-id-2",
|
||||
Name: VaultTitleEmployee,
|
||||
CreatedAt: now,
|
||||
},
|
||||
}, nil)
|
||||
return mockConnectClient
|
||||
},
|
||||
check: func(t *testing.T, vaults []model.Vault, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, vaults, 2)
|
||||
// Check the first vault
|
||||
require.Equal(t, "test-id", vaults[0].ID)
|
||||
require.Equal(t, now, vaults[0].CreatedAt)
|
||||
// Check the second vault
|
||||
require.Equal(t, "test-id-2", vaults[1].ID)
|
||||
require.Equal(t, now, vaults[1].CreatedAt)
|
||||
},
|
||||
},
|
||||
"should return an error": {
|
||||
mockClient: func() *mock.ConnectClientMock {
|
||||
mockConnectClient := &mock.ConnectClientMock{}
|
||||
mockConnectClient.On("GetVaultsByTitle", VaultTitleEmployee).Return([]onepassword.Vault{}, errors.New("error"))
|
||||
return mockConnectClient
|
||||
},
|
||||
check: func(t *testing.T, vaults []model.Vault, err error) {
|
||||
require.Error(t, err)
|
||||
require.Empty(t, vaults)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for description, tc := range testCases {
|
||||
t.Run(description, func(t *testing.T) {
|
||||
client := &Connect{client: tc.mockClient()}
|
||||
vault, err := client.GetVaultsByTitle(context.Background(), VaultTitleEmployee)
|
||||
tc.check(t, vault, err)
|
||||
})
|
||||
}
|
||||
}
|
97
pkg/onepassword/client/sdk/sdk.go
Normal file
97
pkg/onepassword/client/sdk/sdk.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
sdk "github.com/1password/onepassword-sdk-go"
|
||||
)
|
||||
|
||||
// Config holds the configuration for the 1Password SDK client.
|
||||
type Config struct {
|
||||
ServiceAccountToken string
|
||||
IntegrationName string
|
||||
IntegrationVersion string
|
||||
}
|
||||
|
||||
// SDK is a client for interacting with 1Password using the SDK.
|
||||
type SDK struct {
|
||||
client *sdk.Client
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, config Config) (*SDK, error) {
|
||||
client, err := sdk.NewClient(ctx,
|
||||
sdk.WithServiceAccountToken(config.ServiceAccountToken),
|
||||
sdk.WithIntegrationInfo(config.IntegrationName, config.IntegrationVersion),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("1Password sdk error: %w", err)
|
||||
}
|
||||
|
||||
return &SDK{
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SDK) GetItemByID(ctx context.Context, vaultID, itemID string) (*model.Item, error) {
|
||||
sdkItem, err := s.client.Items().Get(ctx, vaultID, itemID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("1Password sdk error: %w", err)
|
||||
}
|
||||
|
||||
var item model.Item
|
||||
item.FromSDKItem(&sdkItem)
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (s *SDK) GetItemsByTitle(ctx context.Context, vaultID, itemTitle string) ([]model.Item, error) {
|
||||
// Get all items in the vault
|
||||
sdkItems, err := s.client.Items().List(ctx, vaultID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("1Password sdk error: %w", err)
|
||||
}
|
||||
|
||||
// Filter items by title
|
||||
var items []model.Item
|
||||
for _, sdkItem := range sdkItems {
|
||||
if sdkItem.Title == itemTitle {
|
||||
var item model.Item
|
||||
item.FromSDKItemOverview(&sdkItem)
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *SDK) GetFileContent(ctx context.Context, vaultID, itemID, fileID string) ([]byte, error) {
|
||||
bytes, err := s.client.Items().Files().Read(ctx, vaultID, itemID, sdk.FileAttributes{
|
||||
ID: fileID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("1Password sdk error: %w", err)
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func (s *SDK) GetVaultsByTitle(ctx context.Context, title string) ([]model.Vault, error) {
|
||||
// List all vaults
|
||||
sdkVaults, err := s.client.Vaults().List(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("1Password sdk error: %w", err)
|
||||
}
|
||||
|
||||
// Filter vaults by title
|
||||
var vaults []model.Vault
|
||||
for _, sdkVault := range sdkVaults {
|
||||
if sdkVault.Title == title {
|
||||
var vault model.Vault
|
||||
vault.FromSDKVault(&sdkVault)
|
||||
vaults = append(vaults, vault)
|
||||
}
|
||||
}
|
||||
|
||||
return vaults, nil
|
||||
}
|
288
pkg/onepassword/client/sdk/sdk_test.go
Normal file
288
pkg/onepassword/client/sdk/sdk_test.go
Normal file
@@ -0,0 +1,288 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
clienttesting "github.com/1Password/onepassword-operator/pkg/onepassword/client/testing"
|
||||
clientmock "github.com/1Password/onepassword-operator/pkg/onepassword/client/testing/mock"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
sdk "github.com/1password/onepassword-sdk-go"
|
||||
)
|
||||
|
||||
const VaultTitleEmployee = "Employee"
|
||||
|
||||
func TestSDK_GetItemByID(t *testing.T) {
|
||||
sdkItem := clienttesting.CreateSDKItem()
|
||||
|
||||
testCases := map[string]struct {
|
||||
mockItemAPI func() *clientmock.ItemAPIMock
|
||||
check func(t *testing.T, item *model.Item, err error)
|
||||
}{
|
||||
"should return a single item": {
|
||||
mockItemAPI: func() *clientmock.ItemAPIMock {
|
||||
m := &clientmock.ItemAPIMock{}
|
||||
m.On("Get", context.Background(), "vault-id", "item-id").Return(*sdkItem, nil)
|
||||
return m
|
||||
},
|
||||
check: func(t *testing.T, item *model.Item, err error) {
|
||||
require.NoError(t, err)
|
||||
clienttesting.CheckSDKItemMapping(t, sdkItem, item)
|
||||
},
|
||||
},
|
||||
"should return an error": {
|
||||
mockItemAPI: func() *clientmock.ItemAPIMock {
|
||||
m := &clientmock.ItemAPIMock{}
|
||||
m.On("Get", context.Background(), "vault-id", "item-id").Return(sdk.Item{}, errors.New("error"))
|
||||
return m
|
||||
},
|
||||
check: func(t *testing.T, item *model.Item, err error) {
|
||||
require.Error(t, err)
|
||||
require.Empty(t, item)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for description, tc := range testCases {
|
||||
t.Run(description, func(t *testing.T) {
|
||||
client := &SDK{
|
||||
client: &sdk.Client{
|
||||
ItemsAPI: tc.mockItemAPI(),
|
||||
},
|
||||
}
|
||||
item, err := client.GetItemByID(context.Background(), "vault-id", "item-id")
|
||||
tc.check(t, item, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSDK_GetItemsByTitle(t *testing.T) {
|
||||
sdkItem1 := clienttesting.CreateSDKItemOverview()
|
||||
sdkItem2 := clienttesting.CreateSDKItemOverview()
|
||||
|
||||
testCases := map[string]struct {
|
||||
mockItemAPI func() *clientmock.ItemAPIMock
|
||||
check func(t *testing.T, items []model.Item, err error)
|
||||
}{
|
||||
"should return a single item": {
|
||||
mockItemAPI: func() *clientmock.ItemAPIMock {
|
||||
m := &clientmock.ItemAPIMock{}
|
||||
|
||||
copySDKItem2 := *sdkItem2
|
||||
copySDKItem2.Title = "Some other item"
|
||||
|
||||
m.On("List", context.Background(), "vault-id", mock.Anything).Return([]sdk.ItemOverview{
|
||||
*sdkItem1,
|
||||
copySDKItem2,
|
||||
}, nil)
|
||||
return m
|
||||
},
|
||||
check: func(t *testing.T, items []model.Item, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, items, 1)
|
||||
clienttesting.CheckSDKItemOverviewMapping(t, sdkItem1, &items[0])
|
||||
},
|
||||
},
|
||||
"should return a two items": {
|
||||
mockItemAPI: func() *clientmock.ItemAPIMock {
|
||||
m := &clientmock.ItemAPIMock{}
|
||||
m.On("List", context.Background(), "vault-id", mock.Anything).Return([]sdk.ItemOverview{
|
||||
*sdkItem1,
|
||||
*sdkItem2,
|
||||
}, nil)
|
||||
return m
|
||||
},
|
||||
check: func(t *testing.T, items []model.Item, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, items, 2)
|
||||
clienttesting.CheckSDKItemOverviewMapping(t, sdkItem1, &items[0])
|
||||
clienttesting.CheckSDKItemOverviewMapping(t, sdkItem2, &items[1])
|
||||
},
|
||||
},
|
||||
"should return empty list": {
|
||||
mockItemAPI: func() *clientmock.ItemAPIMock {
|
||||
m := &clientmock.ItemAPIMock{}
|
||||
m.On("List", context.Background(), "vault-id", mock.Anything).Return([]sdk.ItemOverview{}, nil)
|
||||
return m
|
||||
},
|
||||
check: func(t *testing.T, items []model.Item, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, items, 0)
|
||||
},
|
||||
},
|
||||
"should return an error": {
|
||||
mockItemAPI: func() *clientmock.ItemAPIMock {
|
||||
m := &clientmock.ItemAPIMock{}
|
||||
m.On("List", context.Background(), "vault-id", mock.Anything).Return([]sdk.ItemOverview{}, errors.New("error"))
|
||||
return m
|
||||
},
|
||||
check: func(t *testing.T, items []model.Item, err error) {
|
||||
require.Error(t, err)
|
||||
require.Empty(t, items)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for description, tc := range testCases {
|
||||
t.Run(description, func(t *testing.T) {
|
||||
client := &SDK{
|
||||
client: &sdk.Client{
|
||||
ItemsAPI: tc.mockItemAPI(),
|
||||
},
|
||||
}
|
||||
items, err := client.GetItemsByTitle(context.Background(), "vault-id", "item-title")
|
||||
tc.check(t, items, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSDK_GetFileContent(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
mockItemAPI func() *clientmock.ItemAPIMock
|
||||
check func(t *testing.T, content []byte, err error)
|
||||
}{
|
||||
"should return file content": {
|
||||
mockItemAPI: func() *clientmock.ItemAPIMock {
|
||||
fileMock := &clientmock.FileAPIMock{}
|
||||
fileMock.On("Read", mock.Anything, "vault-id", "item-id",
|
||||
mock.MatchedBy(func(attr sdk.FileAttributes) bool {
|
||||
return attr.ID == "file-id"
|
||||
}),
|
||||
).Return([]byte("file content"), nil)
|
||||
|
||||
itemMock := &clientmock.ItemAPIMock{
|
||||
FilesAPI: fileMock,
|
||||
}
|
||||
itemMock.On("Files").Return(fileMock)
|
||||
|
||||
return itemMock
|
||||
},
|
||||
check: func(t *testing.T, content []byte, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("file content"), content)
|
||||
},
|
||||
},
|
||||
"should return an error": {
|
||||
mockItemAPI: func() *clientmock.ItemAPIMock {
|
||||
fileMock := &clientmock.FileAPIMock{}
|
||||
fileMock.On("Read", mock.Anything, "vault-id", "item-id",
|
||||
mock.MatchedBy(func(attr sdk.FileAttributes) bool {
|
||||
return attr.ID == "file-id"
|
||||
}),
|
||||
).Return(nil, errors.New("error"))
|
||||
|
||||
itemMock := &clientmock.ItemAPIMock{
|
||||
FilesAPI: fileMock,
|
||||
}
|
||||
itemMock.On("Files").Return(fileMock)
|
||||
|
||||
return itemMock
|
||||
},
|
||||
check: func(t *testing.T, content []byte, err error) {
|
||||
require.Error(t, err)
|
||||
require.Nil(t, content)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for description, tc := range testCases {
|
||||
t.Run(description, func(t *testing.T) {
|
||||
client := &SDK{
|
||||
client: &sdk.Client{
|
||||
ItemsAPI: tc.mockItemAPI(),
|
||||
},
|
||||
}
|
||||
content, err := client.GetFileContent(context.Background(), "vault-id", "item-id", "file-id")
|
||||
tc.check(t, content, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSDK_GetVaultsByTitle(t *testing.T) {
|
||||
now := time.Now()
|
||||
testCases := map[string]struct {
|
||||
mockVaultAPI func() *clientmock.VaultAPIMock
|
||||
check func(t *testing.T, vaults []model.Vault, err error)
|
||||
}{
|
||||
"should return a single vault": {
|
||||
mockVaultAPI: func() *clientmock.VaultAPIMock {
|
||||
m := &clientmock.VaultAPIMock{}
|
||||
m.On("List", context.Background()).Return([]sdk.VaultOverview{
|
||||
{
|
||||
ID: "test-id",
|
||||
Title: VaultTitleEmployee,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
ID: "test-id-2",
|
||||
Title: "Some other vault",
|
||||
CreatedAt: now,
|
||||
},
|
||||
}, nil)
|
||||
return m
|
||||
},
|
||||
check: func(t *testing.T, vaults []model.Vault, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, vaults, 1)
|
||||
require.Equal(t, "test-id", vaults[0].ID)
|
||||
require.Equal(t, now, vaults[0].CreatedAt)
|
||||
},
|
||||
},
|
||||
"should return a two vaults": {
|
||||
mockVaultAPI: func() *clientmock.VaultAPIMock {
|
||||
m := &clientmock.VaultAPIMock{}
|
||||
m.On("List", context.Background()).Return([]sdk.VaultOverview{
|
||||
{
|
||||
ID: "test-id",
|
||||
Title: VaultTitleEmployee,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
ID: "test-id-2",
|
||||
Title: VaultTitleEmployee,
|
||||
CreatedAt: now,
|
||||
},
|
||||
}, nil)
|
||||
return m
|
||||
},
|
||||
check: func(t *testing.T, vaults []model.Vault, err error) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, vaults, 2)
|
||||
// Check the first vault
|
||||
require.Equal(t, "test-id", vaults[0].ID)
|
||||
require.Equal(t, now, vaults[0].CreatedAt)
|
||||
// Check the second vault
|
||||
require.Equal(t, "test-id-2", vaults[1].ID)
|
||||
require.Equal(t, now, vaults[1].CreatedAt)
|
||||
},
|
||||
},
|
||||
"should return an error": {
|
||||
mockVaultAPI: func() *clientmock.VaultAPIMock {
|
||||
m := &clientmock.VaultAPIMock{}
|
||||
m.On("List", context.Background()).Return([]sdk.VaultOverview{}, errors.New("error"))
|
||||
return m
|
||||
},
|
||||
check: func(t *testing.T, vaults []model.Vault, err error) {
|
||||
require.Error(t, err)
|
||||
require.Empty(t, vaults)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for description, tc := range testCases {
|
||||
t.Run(description, func(t *testing.T) {
|
||||
client := &SDK{
|
||||
client: &sdk.Client{
|
||||
VaultsAPI: tc.mockVaultAPI(),
|
||||
},
|
||||
}
|
||||
vault, err := client.GetVaultsByTitle(context.Background(), VaultTitleEmployee)
|
||||
tc.check(t, vault, err)
|
||||
})
|
||||
}
|
||||
}
|
110
pkg/onepassword/client/testing/item.go
Normal file
110
pkg/onepassword/client/testing/item.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
sdk "github.com/1password/onepassword-sdk-go"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
)
|
||||
|
||||
func CreateConnectItem() *onepassword.Item {
|
||||
return &onepassword.Item{
|
||||
ID: "test-id",
|
||||
Vault: onepassword.ItemVault{ID: "test-vault-id"},
|
||||
Version: 1,
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
Fields: []*onepassword.ItemField{
|
||||
{Label: "label1", Value: "value1"},
|
||||
{Label: "label2", Value: "value2"},
|
||||
},
|
||||
Files: []*onepassword.File{
|
||||
{ID: "file-id-1", Name: "file1.txt", Size: 1234},
|
||||
{ID: "file-id-2", Name: "file2.txt", Size: 1234},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreateSDKItem() *sdk.Item {
|
||||
return &sdk.Item{
|
||||
ID: "test-id",
|
||||
VaultID: "test-vault-id",
|
||||
Version: 1,
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
Fields: []sdk.ItemField{
|
||||
{Title: "label1", Value: "value1"},
|
||||
{Title: "label2", Value: "value2"},
|
||||
},
|
||||
Files: []sdk.ItemFile{
|
||||
{Attributes: sdk.FileAttributes{ID: "file-id-1", Name: "file1.txt", Size: 1234}},
|
||||
{Attributes: sdk.FileAttributes{ID: "file-id-2", Name: "file2.txt", Size: 1234}},
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func CreateSDKItemOverview() *sdk.ItemOverview {
|
||||
return &sdk.ItemOverview{
|
||||
ID: "test-id",
|
||||
Title: "item-title",
|
||||
VaultID: "test-vault-id",
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func CheckConnectItemMapping(t *testing.T, expected *onepassword.Item, actual *model.Item) {
|
||||
t.Helper()
|
||||
|
||||
require.Equal(t, expected.ID, actual.ID)
|
||||
require.Equal(t, expected.Vault.ID, actual.VaultID)
|
||||
require.Equal(t, expected.Version, actual.Version)
|
||||
require.ElementsMatch(t, expected.Tags, actual.Tags)
|
||||
|
||||
for i, field := range expected.Fields {
|
||||
require.Equal(t, field.Label, actual.Fields[i].Label)
|
||||
require.Equal(t, field.Value, actual.Fields[i].Value)
|
||||
}
|
||||
|
||||
for i, file := range expected.Files {
|
||||
require.Equal(t, file.ID, actual.Files[i].ID)
|
||||
require.Equal(t, file.Name, actual.Files[i].Name)
|
||||
require.Equal(t, file.Size, actual.Files[i].Size)
|
||||
}
|
||||
|
||||
require.Equal(t, expected.CreatedAt, actual.CreatedAt)
|
||||
}
|
||||
|
||||
func CheckSDKItemMapping(t *testing.T, expected *sdk.Item, actual *model.Item) {
|
||||
t.Helper()
|
||||
|
||||
require.Equal(t, expected.ID, actual.ID)
|
||||
require.Equal(t, expected.VaultID, actual.VaultID)
|
||||
require.Equal(t, int(expected.Version), actual.Version)
|
||||
require.ElementsMatch(t, expected.Tags, actual.Tags)
|
||||
|
||||
for i, field := range expected.Fields {
|
||||
require.Equal(t, field.Title, actual.Fields[i].Label)
|
||||
require.Equal(t, field.Value, actual.Fields[i].Value)
|
||||
}
|
||||
|
||||
for i, file := range expected.Files {
|
||||
require.Equal(t, file.Attributes.ID, actual.Files[i].ID)
|
||||
require.Equal(t, file.Attributes.Name, actual.Files[i].Name)
|
||||
require.Equal(t, int(file.Attributes.Size), actual.Files[i].Size)
|
||||
}
|
||||
|
||||
require.Equal(t, expected.CreatedAt, actual.CreatedAt)
|
||||
}
|
||||
|
||||
func CheckSDKItemOverviewMapping(t *testing.T, expected *sdk.ItemOverview, actual *model.Item) {
|
||||
t.Helper()
|
||||
|
||||
require.Equal(t, expected.ID, actual.ID)
|
||||
require.Equal(t, expected.VaultID, actual.VaultID)
|
||||
require.ElementsMatch(t, expected.Tags, actual.Tags)
|
||||
require.Equal(t, expected.CreatedAt, actual.CreatedAt)
|
||||
}
|
130
pkg/onepassword/client/testing/mock/connect.go
Normal file
130
pkg/onepassword/client/testing/mock/connect.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
)
|
||||
|
||||
// ConnectClientMock is a mock implementation of the ConnectClient interface
|
||||
type ConnectClientMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetVaults() ([]onepassword.Vault, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetVault(uuid string) (*onepassword.Vault, error) {
|
||||
args := c.Called(uuid)
|
||||
return args.Get(0).(*onepassword.Vault), args.Error(1)
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetVaultByUUID(uuid string) (*onepassword.Vault, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetVaultByTitle(title string) (*onepassword.Vault, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetVaultsByTitle(title string) ([]onepassword.Vault, error) {
|
||||
args := c.Called(title)
|
||||
return args.Get(0).([]onepassword.Vault), args.Error(1)
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetItems(vaultQuery string) ([]onepassword.Item, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetItem(itemQuery, vaultQuery string) (*onepassword.Item, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetItemByUUID(uuid string, vaultQuery string) (*onepassword.Item, error) {
|
||||
args := c.Called(uuid, vaultQuery)
|
||||
return args.Get(0).(*onepassword.Item), args.Error(1)
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetItemByTitle(title string, vaultQuery string) (*onepassword.Item, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetItemsByTitle(title string, vaultQuery string) ([]onepassword.Item, error) {
|
||||
args := c.Called(title, vaultQuery)
|
||||
return args.Get(0).([]onepassword.Item), args.Error(1)
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) UpdateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) DeleteItem(item *onepassword.Item, vaultQuery string) error {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) DeleteItemByID(itemUUID string, vaultQuery string) error {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) DeleteItemByTitle(title string, vaultQuery string) error {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetFiles(itemQuery string, vaultQuery string) ([]onepassword.File, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetFile(uuid string, itemQuery string, vaultQuery string) (*onepassword.File, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) GetFileContent(file *onepassword.File) ([]byte, error) {
|
||||
args := c.Called(file)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]byte), args.Error(1)
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) DownloadFile(file *onepassword.File, targetDirectory string, overwrite bool) (string, error) {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) LoadStructFromItemByUUID(config interface{}, itemUUID string, vaultQuery string) error {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) LoadStructFromItemByTitle(config interface{}, itemTitle string, vaultQuery string) error {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) LoadStructFromItem(config interface{}, itemQuery string, vaultQuery string) error {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConnectClientMock) LoadStruct(config interface{}) error {
|
||||
// Only implement this if mocking is needed
|
||||
panic("implement me")
|
||||
}
|
89
pkg/onepassword/client/testing/mock/sdk.go
Normal file
89
pkg/onepassword/client/testing/mock/sdk.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
sdk "github.com/1password/onepassword-sdk-go"
|
||||
)
|
||||
|
||||
type VaultAPIMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (v *VaultAPIMock) List(ctx context.Context) ([]sdk.VaultOverview, error) {
|
||||
args := v.Called(ctx)
|
||||
return args.Get(0).([]sdk.VaultOverview), args.Error(1)
|
||||
}
|
||||
|
||||
type ItemAPIMock struct {
|
||||
mock.Mock
|
||||
FilesAPI sdk.ItemsFilesAPI
|
||||
}
|
||||
|
||||
func (i *ItemAPIMock) Create(ctx context.Context, params sdk.ItemCreateParams) (sdk.Item, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (i *ItemAPIMock) Get(ctx context.Context, vaultID string, itemID string) (sdk.Item, error) {
|
||||
args := i.Called(ctx, vaultID, itemID)
|
||||
return args.Get(0).(sdk.Item), args.Error(1)
|
||||
}
|
||||
|
||||
func (i *ItemAPIMock) Put(ctx context.Context, item sdk.Item) (sdk.Item, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (i *ItemAPIMock) Delete(ctx context.Context, vaultID string, itemID string) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (i *ItemAPIMock) Archive(ctx context.Context, vaultID string, itemID string) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (i *ItemAPIMock) List(ctx context.Context, vaultID string, filters ...sdk.ItemListFilter) ([]sdk.ItemOverview, error) {
|
||||
args := i.Called(ctx, vaultID, filters)
|
||||
return args.Get(0).([]sdk.ItemOverview), args.Error(1)
|
||||
}
|
||||
|
||||
func (i *ItemAPIMock) Shares() sdk.ItemsSharesAPI {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (i *ItemAPIMock) Files() sdk.ItemsFilesAPI {
|
||||
return i.FilesAPI
|
||||
}
|
||||
|
||||
type FileAPIMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (f *FileAPIMock) Attach(ctx context.Context, item sdk.Item, fileParams sdk.FileCreateParams) (sdk.Item, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (f *FileAPIMock) Delete(ctx context.Context, item sdk.Item, sectionID string, fieldID string) (sdk.Item, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (f *FileAPIMock) ReplaceDocument(ctx context.Context, item sdk.Item, docParams sdk.DocumentCreateParams) (sdk.Item, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (f *FileAPIMock) Read(ctx context.Context, vaultID, itemID string, attributes sdk.FileAttributes) ([]byte, error) {
|
||||
args := f.Called(ctx, vaultID, itemID, attributes)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]byte), args.Error(1)
|
||||
}
|
@@ -15,16 +15,16 @@ import (
|
||||
)
|
||||
|
||||
var logConnectSetup = logf.Log.WithName("ConnectSetup")
|
||||
var deploymentPath = "config/connect/deployment.yaml"
|
||||
var servicePath = "config/connect/service.yaml"
|
||||
var deploymentPath = "../config/connect/deployment.yaml"
|
||||
var servicePath = "../config/connect/service.yaml"
|
||||
|
||||
func SetupConnect(kubeClient client.Client, deploymentNamespace string) error {
|
||||
err := setupService(kubeClient, servicePath, deploymentNamespace)
|
||||
func SetupConnect(ctx context.Context, kubeClient client.Client, deploymentNamespace string) error {
|
||||
err := setupService(ctx, kubeClient, servicePath, deploymentNamespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = setupDeployment(kubeClient, deploymentPath, deploymentNamespace)
|
||||
err = setupDeployment(ctx, kubeClient, deploymentPath, deploymentNamespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -32,27 +32,27 @@ func SetupConnect(kubeClient client.Client, deploymentNamespace string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupDeployment(kubeClient client.Client, deploymentPath string, deploymentNamespace string) error {
|
||||
func setupDeployment(ctx context.Context, 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)
|
||||
err := kubeClient.Get(ctx, 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 createDeployment(ctx, kubeClient, deploymentPath, deploymentNamespace)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func createDeployment(kubeClient client.Client, deploymentPath string, deploymentNamespace string) error {
|
||||
func createDeployment(ctx context.Context, kubeClient client.Client, deploymentPath string, deploymentNamespace string) error {
|
||||
deployment, err := getDeploymentToCreate(deploymentPath, deploymentNamespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = kubeClient.Create(context.Background(), deployment)
|
||||
err = kubeClient.Create(ctx, deployment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -78,21 +78,21 @@ func getDeploymentToCreate(deploymentPath string, deploymentNamespace string) (*
|
||||
return deployment, nil
|
||||
}
|
||||
|
||||
func setupService(kubeClient client.Client, servicePath string, deploymentNamespace string) error {
|
||||
func setupService(ctx context.Context, 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)
|
||||
err := kubeClient.Get(ctx, 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 createService(ctx, kubeClient, servicePath, deploymentNamespace)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func createService(kubeClient client.Client, servicePath string, deploymentNamespace string) error {
|
||||
func createService(ctx context.Context, kubeClient client.Client, servicePath string, deploymentNamespace string) error {
|
||||
f, err := os.Open(servicePath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -108,7 +108,7 @@ func createService(kubeClient client.Client, servicePath string, deploymentNames
|
||||
return err
|
||||
}
|
||||
|
||||
err = kubeClient.Create(context.Background(), service)
|
||||
err = kubeClient.Create(ctx, service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ import (
|
||||
var defaultNamespacedName = types.NamespacedName{Name: "onepassword-connect", Namespace: "default"}
|
||||
|
||||
func TestServiceSetup(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Register operator types with the runtime scheme.
|
||||
s := scheme.Scheme
|
||||
@@ -25,7 +26,7 @@ func TestServiceSetup(t *testing.T) {
|
||||
// Create a fake client to mock API calls.
|
||||
client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
|
||||
|
||||
err := setupService(client, "../../config/connect/service.yaml", defaultNamespacedName.Namespace)
|
||||
err := setupService(ctx, client, "../../config/connect/service.yaml", defaultNamespacedName.Namespace)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error Setting Up Connect: %v", err)
|
||||
@@ -33,13 +34,14 @@ func TestServiceSetup(t *testing.T) {
|
||||
|
||||
// check that service was created
|
||||
service := &corev1.Service{}
|
||||
err = client.Get(context.TODO(), defaultNamespacedName, service)
|
||||
err = client.Get(ctx, defaultNamespacedName, service)
|
||||
if err != nil {
|
||||
t.Errorf("Error Setting Up Connect service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentSetup(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Register operator types with the runtime scheme.
|
||||
s := scheme.Scheme
|
||||
@@ -50,7 +52,7 @@ func TestDeploymentSetup(t *testing.T) {
|
||||
// Create a fake client to mock API calls.
|
||||
client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
|
||||
|
||||
err := setupDeployment(client, "../../config/connect/deployment.yaml", defaultNamespacedName.Namespace)
|
||||
err := setupDeployment(ctx, client, "../../config/connect/deployment.yaml", defaultNamespacedName.Namespace)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error Setting Up Connect: %v", err)
|
||||
@@ -58,7 +60,7 @@ func TestDeploymentSetup(t *testing.T) {
|
||||
|
||||
// check that deployment was created
|
||||
deployment := &appsv1.Deployment{}
|
||||
err = client.Get(context.TODO(), defaultNamespacedName, deployment)
|
||||
err = client.Get(ctx, defaultNamespacedName, deployment)
|
||||
if err != nil {
|
||||
t.Errorf("Error Setting Up Connect deployment: %v", err)
|
||||
}
|
||||
|
@@ -9,18 +9,30 @@ import (
|
||||
|
||||
func TestIsDeploymentUsingSecretsUsingVolumes(t *testing.T) {
|
||||
secretNamesToSearch := map[string]*corev1.Secret{
|
||||
"onepassword-database-secret": {},
|
||||
"onepassword-api-key": {},
|
||||
"onepassword-database-secret": {},
|
||||
"onepassword-api-key": {},
|
||||
"onepassword-app-token": {},
|
||||
"onepassword-user-credentials": {},
|
||||
}
|
||||
|
||||
volumeSecretNames := []string{
|
||||
"onepassword-database-secret",
|
||||
"onepassword-api-key",
|
||||
"some_other_key",
|
||||
}
|
||||
|
||||
volumes := generateVolumes(volumeSecretNames)
|
||||
|
||||
volumeProjectedSecretNames := []string{
|
||||
"onepassword-app-token",
|
||||
"onepassword-user-credentials",
|
||||
}
|
||||
|
||||
volumeProjected := generateVolumesProjected(volumeProjectedSecretNames)
|
||||
|
||||
volumes = append(volumes, volumeProjected)
|
||||
|
||||
deployment := &appsv1.Deployment{}
|
||||
deployment.Spec.Template.Spec.Volumes = generateVolumes(volumeSecretNames)
|
||||
deployment.Spec.Template.Spec.Volumes = volumes
|
||||
if !IsDeploymentUsingSecrets(deployment, secretNamesToSearch) {
|
||||
t.Errorf("Expected that deployment was using secrets but they were not detected.")
|
||||
}
|
||||
|
@@ -1,39 +1,40 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/connect"
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
opclient "github.com/1Password/onepassword-operator/pkg/onepassword/client"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
)
|
||||
|
||||
var logger = logf.Log.WithName("retrieve_item")
|
||||
|
||||
func GetOnePasswordItemByPath(opConnectClient connect.Client, path string) (*onepassword.Item, error) {
|
||||
vaultValue, itemValue, err := ParseVaultAndItemFromPath(path)
|
||||
func GetOnePasswordItemByPath(ctx context.Context, opClient opclient.Client, path string) (*model.Item, error) {
|
||||
vaultNameOrID, itemNameOrID, err := ParseVaultAndItemFromPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vaultId, err := getVaultId(opConnectClient, vaultValue)
|
||||
vaultID, err := getVaultID(ctx, opClient, vaultNameOrID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to 'getVaultID' for vaultNameOrID='%s': %w", vaultNameOrID, err)
|
||||
}
|
||||
|
||||
itemId, err := getItemId(opConnectClient, itemValue, vaultId)
|
||||
itemID, err := getItemID(ctx, opClient, vaultID, itemNameOrID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("faild to 'getItemID' for vaultID='%s' and itemNameOrID='%s': %w", vaultID, itemNameOrID, err)
|
||||
}
|
||||
|
||||
item, err := opConnectClient.GetItem(itemId, vaultId)
|
||||
item, err := opClient.GetItemByID(ctx, vaultID, itemID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("faield to 'GetItemByID' for vaultID='%s' and itemID='%s': %w", vaultID, itemID, err)
|
||||
}
|
||||
|
||||
for _, file := range item.Files {
|
||||
_, err := opConnectClient.GetFileContent(file)
|
||||
_, err := opClient.GetFileContent(ctx, vaultID, itemID, file.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -50,15 +51,15 @@ func ParseVaultAndItemFromPath(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 getVaultId(client connect.Client, vaultIdentifier string) (string, error) {
|
||||
if !IsValidClientUUID(vaultIdentifier) {
|
||||
vaults, err := client.GetVaultsByTitle(vaultIdentifier)
|
||||
func getVaultID(ctx context.Context, client opclient.Client, vaultNameOrID string) (string, error) {
|
||||
if !IsValidClientUUID(vaultNameOrID) {
|
||||
vaults, err := client.GetVaultsByTitle(ctx, vaultNameOrID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(vaults) == 0 {
|
||||
return "", fmt.Errorf("No vaults found with identifier %q", vaultIdentifier)
|
||||
return "", fmt.Errorf("No vaults found with identifier %q", vaultNameOrID)
|
||||
}
|
||||
|
||||
oldestVault := vaults[0]
|
||||
@@ -68,22 +69,22 @@ func getVaultId(client connect.Client, vaultIdentifier string) (string, error) {
|
||||
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))
|
||||
logger.Info(fmt.Sprintf("%v 1Password vaults found with the title %q. Will use vault %q as it is the oldest.", len(vaults), vaultNameOrID, oldestVault.ID))
|
||||
}
|
||||
vaultIdentifier = oldestVault.ID
|
||||
vaultNameOrID = oldestVault.ID
|
||||
}
|
||||
return vaultIdentifier, nil
|
||||
return vaultNameOrID, nil
|
||||
}
|
||||
|
||||
func getItemId(client connect.Client, itemIdentifier string, vaultId string) (string, error) {
|
||||
if !IsValidClientUUID(itemIdentifier) {
|
||||
items, err := client.GetItemsByTitle(itemIdentifier, vaultId)
|
||||
func getItemID(ctx context.Context, client opclient.Client, vaultId, itemNameOrID string) (string, error) {
|
||||
if !IsValidClientUUID(itemNameOrID) {
|
||||
items, err := client.GetItemsByTitle(ctx, vaultId, itemNameOrID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
return "", fmt.Errorf("No items found with identifier %q", itemIdentifier)
|
||||
return "", fmt.Errorf("No items found with identifier %q", itemNameOrID)
|
||||
}
|
||||
|
||||
oldestItem := items[0]
|
||||
@@ -93,9 +94,9 @@ func getItemId(client connect.Client, itemIdentifier string, vaultId string) (st
|
||||
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))
|
||||
logger.Info(fmt.Sprintf("%v 1Password items found with the title %q. Will use item %q as it is the oldest.", len(items), itemNameOrID, oldestItem.ID))
|
||||
}
|
||||
itemIdentifier = oldestItem.ID
|
||||
itemNameOrID = oldestItem.ID
|
||||
}
|
||||
return itemIdentifier, nil
|
||||
return itemNameOrID, nil
|
||||
}
|
||||
|
27
pkg/onepassword/model/file.go
Normal file
27
pkg/onepassword/model/file.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// File represents a file stored in 1Password.
|
||||
type File struct {
|
||||
ID string
|
||||
Name string
|
||||
Size int
|
||||
ContentPath string
|
||||
content []byte
|
||||
}
|
||||
|
||||
// Content returns the content of the file if they have been loaded and returns an error if they have not been loaded.
|
||||
// Use `client.GetFileContent(file *File)` instead to make sure the content is fetched automatically if not present.
|
||||
func (f *File) Content() ([]byte, error) {
|
||||
if f.content == nil {
|
||||
return nil, errors.New("file content not loaded")
|
||||
}
|
||||
return f.content, nil
|
||||
}
|
||||
|
||||
func (f *File) SetContent(content []byte) {
|
||||
f.content = content
|
||||
}
|
85
pkg/onepassword/model/item.go
Normal file
85
pkg/onepassword/model/item.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
connect "github.com/1Password/connect-sdk-go/onepassword"
|
||||
sdk "github.com/1password/onepassword-sdk-go"
|
||||
)
|
||||
|
||||
// Item represents 1Password item.
|
||||
type Item struct {
|
||||
ID string
|
||||
VaultID string
|
||||
Version int
|
||||
Tags []string
|
||||
Fields []ItemField
|
||||
Files []File
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// FromConnectItem populates the Item from a Connect item.
|
||||
func (i *Item) FromConnectItem(item *connect.Item) {
|
||||
i.ID = item.ID
|
||||
i.VaultID = item.Vault.ID
|
||||
i.Version = item.Version
|
||||
|
||||
for _, tag := range item.Tags {
|
||||
i.Tags = append(i.Tags, tag)
|
||||
}
|
||||
|
||||
for _, field := range item.Fields {
|
||||
i.Fields = append(i.Fields, ItemField{
|
||||
Label: field.Label,
|
||||
Value: field.Value,
|
||||
})
|
||||
}
|
||||
|
||||
for _, file := range item.Files {
|
||||
i.Files = append(i.Files, File{
|
||||
ID: file.ID,
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
})
|
||||
}
|
||||
|
||||
i.CreatedAt = item.CreatedAt
|
||||
}
|
||||
|
||||
// FromSDKItem populates the Item from an SDK item.
|
||||
func (i *Item) FromSDKItem(item *sdk.Item) {
|
||||
i.ID = item.ID
|
||||
i.VaultID = item.VaultID
|
||||
i.Version = int(item.Version)
|
||||
|
||||
i.Tags = make([]string, len(item.Tags))
|
||||
copy(i.Tags, item.Tags)
|
||||
|
||||
for _, field := range item.Fields {
|
||||
i.Fields = append(i.Fields, ItemField{
|
||||
Label: field.Title,
|
||||
Value: field.Value,
|
||||
})
|
||||
}
|
||||
|
||||
for _, file := range item.Files {
|
||||
i.Files = append(i.Files, File{
|
||||
ID: file.Attributes.ID,
|
||||
Name: file.Attributes.Name,
|
||||
Size: int(file.Attributes.Size),
|
||||
})
|
||||
}
|
||||
|
||||
i.CreatedAt = item.CreatedAt
|
||||
}
|
||||
|
||||
// FromSDKItemOverview populates the Item from an SDK item overview.
|
||||
func (i *Item) FromSDKItemOverview(item *sdk.ItemOverview) {
|
||||
i.ID = item.ID
|
||||
i.VaultID = item.VaultID
|
||||
|
||||
i.Tags = make([]string, len(item.Tags))
|
||||
copy(i.Tags, item.Tags)
|
||||
|
||||
i.CreatedAt = item.CreatedAt
|
||||
}
|
7
pkg/onepassword/model/item_field.go
Normal file
7
pkg/onepassword/model/item_field.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package model
|
||||
|
||||
// ItemField Representation of a single field on an Item
|
||||
type ItemField struct {
|
||||
Label string
|
||||
Value string
|
||||
}
|
108
pkg/onepassword/model/item_test.go
Normal file
108
pkg/onepassword/model/item_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
connect "github.com/1Password/connect-sdk-go/onepassword"
|
||||
sdk "github.com/1password/onepassword-sdk-go"
|
||||
)
|
||||
|
||||
func TestItem_FromConnectItem(t *testing.T) {
|
||||
connectItem := &connect.Item{
|
||||
ID: "test-item-id",
|
||||
Vault: connect.ItemVault{
|
||||
ID: "test-vault-id",
|
||||
},
|
||||
Version: 1,
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
Fields: []*connect.ItemField{
|
||||
{Label: "field1", Value: "value1"},
|
||||
{Label: "field2", Value: "value2"},
|
||||
},
|
||||
Files: []*connect.File{
|
||||
{ID: "file1", Name: "file1.txt", Size: 1234},
|
||||
{ID: "file2", Name: "file2.txt", Size: 1234},
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
item := &Item{}
|
||||
item.FromConnectItem(connectItem)
|
||||
|
||||
require.Equal(t, connectItem.ID, item.ID)
|
||||
require.Equal(t, connectItem.Vault.ID, item.VaultID)
|
||||
require.Equal(t, connectItem.Version, item.Version)
|
||||
require.ElementsMatch(t, connectItem.Tags, item.Tags)
|
||||
|
||||
for i, field := range connectItem.Fields {
|
||||
require.Equal(t, field.Label, item.Fields[i].Label)
|
||||
require.Equal(t, field.Value, item.Fields[i].Value)
|
||||
}
|
||||
|
||||
for i, file := range connectItem.Files {
|
||||
require.Equal(t, file.ID, item.Files[i].ID)
|
||||
require.Equal(t, file.Name, item.Files[i].Name)
|
||||
require.Equal(t, file.Size, item.Files[i].Size)
|
||||
}
|
||||
|
||||
require.Equal(t, connectItem.CreatedAt, item.CreatedAt)
|
||||
}
|
||||
|
||||
func TestItem_FromSDKItem(t *testing.T) {
|
||||
sdkItem := &sdk.Item{
|
||||
ID: "test-item-id",
|
||||
VaultID: "test-vault-id",
|
||||
Version: 1,
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
Fields: []sdk.ItemField{
|
||||
{ID: "1", Title: "field1", Value: "value1"},
|
||||
{ID: "2", Title: "field2", Value: "value2"},
|
||||
},
|
||||
Files: []sdk.ItemFile{
|
||||
{Attributes: sdk.FileAttributes{Name: "file1.txt", Size: 1234}, FieldID: "file1"},
|
||||
{Attributes: sdk.FileAttributes{Name: "file2.txt", Size: 1234}, FieldID: "file2"},
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
item := &Item{}
|
||||
item.FromSDKItem(sdkItem)
|
||||
|
||||
require.Equal(t, sdkItem.ID, item.ID)
|
||||
require.Equal(t, sdkItem.VaultID, item.VaultID)
|
||||
require.Equal(t, int(sdkItem.Version), item.Version)
|
||||
require.ElementsMatch(t, sdkItem.Tags, item.Tags)
|
||||
|
||||
for i, field := range sdkItem.Fields {
|
||||
require.Equal(t, field.Title, item.Fields[i].Label)
|
||||
require.Equal(t, field.Value, item.Fields[i].Value)
|
||||
}
|
||||
|
||||
for i, file := range sdkItem.Files {
|
||||
require.Equal(t, file.Attributes.ID, item.Files[i].ID)
|
||||
require.Equal(t, file.Attributes.Name, item.Files[i].Name)
|
||||
require.Equal(t, int(file.Attributes.Size), item.Files[i].Size)
|
||||
}
|
||||
|
||||
require.Equal(t, sdkItem.CreatedAt, item.CreatedAt)
|
||||
}
|
||||
|
||||
func TestItem_FromSDKItemOverview(t *testing.T) {
|
||||
sdkItemOverview := &sdk.ItemOverview{
|
||||
ID: "test-item-id",
|
||||
VaultID: "test-vault-id",
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
item := &Item{}
|
||||
item.FromSDKItemOverview(sdkItemOverview)
|
||||
|
||||
require.Equal(t, sdkItemOverview.ID, item.ID)
|
||||
require.Equal(t, sdkItemOverview.VaultID, item.VaultID)
|
||||
require.ElementsMatch(t, sdkItemOverview.Tags, item.Tags)
|
||||
require.Equal(t, sdkItemOverview.CreatedAt, item.CreatedAt)
|
||||
}
|
23
pkg/onepassword/model/vault.go
Normal file
23
pkg/onepassword/model/vault.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
connect "github.com/1Password/connect-sdk-go/onepassword"
|
||||
sdk "github.com/1password/onepassword-sdk-go"
|
||||
)
|
||||
|
||||
type Vault struct {
|
||||
ID string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func (v *Vault) FromConnectVault(vault *connect.Vault) {
|
||||
v.ID = vault.ID
|
||||
v.CreatedAt = vault.CreatedAt
|
||||
}
|
||||
|
||||
func (v *Vault) FromSDKVault(vault *sdk.VaultOverview) {
|
||||
v.ID = vault.ID
|
||||
v.CreatedAt = vault.CreatedAt
|
||||
}
|
37
pkg/onepassword/model/vault_test.go
Normal file
37
pkg/onepassword/model/vault_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
connect "github.com/1Password/connect-sdk-go/onepassword"
|
||||
sdk "github.com/1password/onepassword-sdk-go"
|
||||
)
|
||||
|
||||
func TestVault_FromConnectVault(t *testing.T) {
|
||||
connectVault := &connect.Vault{
|
||||
ID: "test-id",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
vault := &Vault{}
|
||||
vault.FromConnectVault(connectVault)
|
||||
|
||||
require.Equal(t, connectVault.ID, vault.ID)
|
||||
require.Equal(t, connectVault.CreatedAt, vault.CreatedAt)
|
||||
}
|
||||
|
||||
func TestVault_FromSDKVault(t *testing.T) {
|
||||
sdkVault := &sdk.VaultOverview{
|
||||
ID: "test-id",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
vault := &Vault{}
|
||||
vault.FromSDKVault(sdkVault)
|
||||
|
||||
require.Equal(t, sdkVault.ID, vault.ID)
|
||||
require.Equal(t, sdkVault.CreatedAt, vault.CreatedAt)
|
||||
}
|
@@ -17,6 +17,29 @@ func generateVolumes(names []string) []corev1.Volume {
|
||||
}
|
||||
return volumes
|
||||
}
|
||||
func generateVolumesProjected(names []string) corev1.Volume {
|
||||
volumesProjection := []corev1.VolumeProjection{}
|
||||
for i := 0; i < len(names); i++ {
|
||||
volumeProjection := corev1.VolumeProjection{
|
||||
Secret: &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: names[i],
|
||||
},
|
||||
},
|
||||
}
|
||||
volumesProjection = append(volumesProjection, volumeProjection)
|
||||
}
|
||||
volume := corev1.Volume{
|
||||
Name: "someName",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: volumesProjection,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return volume
|
||||
}
|
||||
func generateContainersWithSecretRefsFromEnv(names []string) []corev1.Container {
|
||||
containers := []corev1.Container{}
|
||||
for i := 0; i < len(names); i++ {
|
||||
|
@@ -7,10 +7,11 @@ import (
|
||||
|
||||
onepasswordv1 "github.com/1Password/onepassword-operator/api/v1"
|
||||
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
|
||||
"github.com/1Password/onepassword-operator/pkg/logs"
|
||||
opclient "github.com/1Password/onepassword-operator/pkg/onepassword/client"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
"github.com/1Password/onepassword-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"
|
||||
@@ -22,37 +23,37 @@ 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 {
|
||||
func NewManager(kubernetesClient client.Client, opClient opclient.Client, shouldAutoRestartDeploymentsGlobal bool) *SecretUpdateHandler {
|
||||
return &SecretUpdateHandler{
|
||||
client: kubernetesClient,
|
||||
opConnectClient: opConnectClient,
|
||||
opClient: opClient,
|
||||
shouldAutoRestartDeploymentsGlobal: shouldAutoRestartDeploymentsGlobal,
|
||||
}
|
||||
}
|
||||
|
||||
type SecretUpdateHandler struct {
|
||||
client client.Client
|
||||
opConnectClient connect.Client
|
||||
opClient opclient.Client
|
||||
shouldAutoRestartDeploymentsGlobal bool
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) UpdateKubernetesSecretsTask() error {
|
||||
updatedKubernetesSecrets, err := h.updateKubernetesSecrets()
|
||||
func (h *SecretUpdateHandler) UpdateKubernetesSecretsTask(ctx context.Context) error {
|
||||
updatedKubernetesSecrets, err := h.updateKubernetesSecrets(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return h.restartDeploymentsWithUpdatedSecrets(updatedKubernetesSecrets)
|
||||
return h.restartDeploymentsWithUpdatedSecrets(ctx, updatedKubernetesSecrets)
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecretsByNamespace map[string]map[string]*corev1.Secret) error {
|
||||
func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(ctx context.Context, updatedSecretsByNamespace map[string]map[string]*corev1.Secret) error {
|
||||
// No secrets to update. Exit
|
||||
if len(updatedSecretsByNamespace) == 0 || updatedSecretsByNamespace == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
deployments := &appsv1.DeploymentList{}
|
||||
err := h.client.List(context.Background(), deployments)
|
||||
err := h.client.List(ctx, deployments)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to list kubernetes deployments")
|
||||
return err
|
||||
@@ -62,7 +63,7 @@ func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecret
|
||||
return nil
|
||||
}
|
||||
|
||||
setForAutoRestartByNamespaceMap, err := h.getIsSetForAutoRestartByNamespaceMap()
|
||||
setForAutoRestartByNamespaceMap, err := h.getIsSetForAutoRestartByNamespaceMap(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -77,32 +78,32 @@ func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecret
|
||||
}
|
||||
for _, secret := range updatedDeploymentSecrets {
|
||||
if isSecretSetForAutoRestart(secret, deployment, setForAutoRestartByNamespaceMap) {
|
||||
h.restartDeployment(deployment)
|
||||
h.restartDeployment(ctx, deployment)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("Deployment %q at namespace %q is up to date", deployment.GetName(), deployment.Namespace))
|
||||
log.V(logs.DebugLevel).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) {
|
||||
func (h *SecretUpdateHandler) restartDeployment(ctx context.Context, 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)
|
||||
err := h.client.Update(ctx, deployment)
|
||||
if err != nil {
|
||||
log.Error(err, "Problem restarting deployment")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]*corev1.Secret, error) {
|
||||
func (h *SecretUpdateHandler) updateKubernetesSecrets(ctx context.Context) (map[string]map[string]*corev1.Secret, error) {
|
||||
secrets := &corev1.SecretList{}
|
||||
err := h.client.List(context.Background(), secrets)
|
||||
err := h.client.List(ctx, secrets)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to list kubernetes secrets")
|
||||
return nil, err
|
||||
@@ -120,21 +121,21 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]*
|
||||
|
||||
OnePasswordItemPath := h.getPathFromOnePasswordItem(secret)
|
||||
|
||||
item, err := GetOnePasswordItemByPath(h.opConnectClient, OnePasswordItemPath)
|
||||
item, err := GetOnePasswordItemByPath(ctx, h.opClient, OnePasswordItemPath)
|
||||
if err != nil {
|
||||
log.Error(err, "failed to retrieve 1Password item at path \"%s\" for secret \"%s\"", secret.Annotations[ItemPathAnnotation], secret.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
itemVersion := fmt.Sprint(item.Version)
|
||||
itemPathString := fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID)
|
||||
itemPathString := fmt.Sprintf("vaults/%v/items/%v", item.VaultID, item.ID)
|
||||
|
||||
if currentVersion != itemVersion || secret.Annotations[ItemPathAnnotation] != itemPathString {
|
||||
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()))
|
||||
log.V(logs.DebugLevel).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
|
||||
secret.Annotations[ItemPathAnnotation] = itemPathString
|
||||
if err := h.client.Update(context.Background(), &secret); err != nil {
|
||||
if err := h.client.Update(ctx, &secret); err != nil {
|
||||
log.Error(err, "failed to update secret %s annotations to version %d: %s", secret.Name, itemVersion, err)
|
||||
continue
|
||||
}
|
||||
@@ -144,8 +145,8 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]*
|
||||
secret.Annotations[VersionAnnotation] = itemVersion
|
||||
secret.Annotations[ItemPathAnnotation] = itemPathString
|
||||
secret.Data = kubeSecrets.BuildKubernetesSecretData(item.Fields, item.Files)
|
||||
log.Info(fmt.Sprintf("New secret path: %v and version: %v", secret.Annotations[ItemPathAnnotation], secret.Annotations[VersionAnnotation]))
|
||||
if err := h.client.Update(context.Background(), &secret); err != nil {
|
||||
log.V(logs.DebugLevel).Info(fmt.Sprintf("New secret path: %v and version: %v", secret.Annotations[ItemPathAnnotation], secret.Annotations[VersionAnnotation]))
|
||||
if err := h.client.Update(ctx, &secret); err != nil {
|
||||
log.Error(err, "failed to update secret %s to version %d: %s", secret.Name, itemVersion, err)
|
||||
continue
|
||||
}
|
||||
@@ -158,7 +159,7 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]*
|
||||
return updatedSecrets, nil
|
||||
}
|
||||
|
||||
func isItemLockedForForcedRestarts(item *onepassword.Item) bool {
|
||||
func isItemLockedForForcedRestarts(item *model.Item) bool {
|
||||
tags := item.Tags
|
||||
for i := 0; i < len(tags); i++ {
|
||||
if tags[i] == lockTag {
|
||||
@@ -176,9 +177,9 @@ func isUpdatedSecret(secretName string, updatedSecrets map[string]*corev1.Secret
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap() (map[string]bool, error) {
|
||||
func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap(ctx context.Context) (map[string]bool, error) {
|
||||
namespaces := &corev1.NamespaceList{}
|
||||
err := h.client.List(context.Background(), namespaces)
|
||||
err := h.client.List(ctx, namespaces)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to list kubernetes namespaces")
|
||||
return nil, err
|
||||
|
@@ -4,11 +4,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/mocks"
|
||||
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
|
||||
|
||||
"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"
|
||||
@@ -784,7 +787,7 @@ var tests = []testUpdateSecretTask{
|
||||
func TestUpdateSecretHandler(t *testing.T) {
|
||||
for _, testData := range tests {
|
||||
t.Run(testData.testName, func(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
// Register operator types with the runtime scheme.
|
||||
s := scheme.Scheme
|
||||
s.AddKnownTypes(appsv1.SchemeGroupVersion, testData.existingDeployment)
|
||||
@@ -802,23 +805,15 @@ func TestUpdateSecretHandler(t *testing.T) {
|
||||
// Create a fake client to mock API calls.
|
||||
cl := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
|
||||
|
||||
opConnectClient := &mocks.TestClient{}
|
||||
mocks.DoGetItemFunc = 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
|
||||
}
|
||||
mockOpClient := &mocks.TestClient{}
|
||||
mockOpClient.On("GetItemByID", mock.Anything, mock.Anything).Return(createItem(), nil)
|
||||
h := &SecretUpdateHandler{
|
||||
client: cl,
|
||||
opConnectClient: opConnectClient,
|
||||
opClient: mockOpClient,
|
||||
shouldAutoRestartDeploymentsGlobal: testData.globalAutoRestartEnabled,
|
||||
}
|
||||
|
||||
err := h.UpdateKubernetesSecretsTask()
|
||||
err := h.UpdateKubernetesSecretsTask(ctx)
|
||||
|
||||
assert.Equal(t, testData.expectedError, err)
|
||||
|
||||
@@ -831,7 +826,7 @@ func TestUpdateSecretHandler(t *testing.T) {
|
||||
|
||||
// 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)
|
||||
err = cl.Get(ctx, types.NamespacedName{Name: expectedSecretName, Namespace: namespace}, secret)
|
||||
|
||||
if testData.expectedResultSecret == nil {
|
||||
assert.Error(t, err)
|
||||
@@ -845,7 +840,7 @@ func TestUpdateSecretHandler(t *testing.T) {
|
||||
|
||||
//check if deployment has been restarted
|
||||
deployment := &appsv1.Deployment{}
|
||||
err = cl.Get(context.TODO(), types.NamespacedName{Name: testData.existingDeployment.Name, Namespace: namespace}, deployment)
|
||||
err = cl.Get(ctx, types.NamespacedName{Name: testData.existingDeployment.Name, Namespace: namespace}, deployment)
|
||||
|
||||
_, ok := deployment.Spec.Template.Annotations[RestartAnnotation]
|
||||
if ok {
|
||||
@@ -879,16 +874,23 @@ func TestIsUpdatedSecret(t *testing.T) {
|
||||
assert.True(t, isUpdatedSecret(secretName, updatedSecrets))
|
||||
}
|
||||
|
||||
func generateFields(username, password string) []*onepassword.ItemField {
|
||||
fields := []*onepassword.ItemField{
|
||||
{
|
||||
Label: "username",
|
||||
Value: username,
|
||||
},
|
||||
{
|
||||
Label: "password",
|
||||
Value: password,
|
||||
func createItem() *model.Item {
|
||||
return &model.Item{
|
||||
ID: itemId,
|
||||
VaultID: vaultId,
|
||||
Version: itemVersion,
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
Fields: []model.ItemField{
|
||||
{
|
||||
Label: "username",
|
||||
Value: username,
|
||||
},
|
||||
{
|
||||
Label: "password",
|
||||
Value: password,
|
||||
},
|
||||
},
|
||||
Files: []model.File{},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
@@ -4,26 +4,55 @@ 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
|
||||
}
|
||||
secret := IsVolumeUsingSecret(volumes[i], secrets)
|
||||
secretProjection := IsVolumeUsingSecretProjection(volumes[i], secrets)
|
||||
if secret == nil && secretProjection == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
if len(volumes) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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
|
||||
secret := IsVolumeUsingSecret(volumes[i], secrets)
|
||||
if secret != nil {
|
||||
updatedDeploymentSecrets[secret.Name] = secret
|
||||
} else {
|
||||
secretProjection := IsVolumeUsingSecretProjection(volumes[i], secrets)
|
||||
if secretProjection != nil {
|
||||
updatedDeploymentSecrets[secretProjection.Name] = secretProjection
|
||||
}
|
||||
}
|
||||
}
|
||||
return updatedDeploymentSecrets
|
||||
}
|
||||
|
||||
func IsVolumeUsingSecret(volume corev1.Volume, secrets map[string]*corev1.Secret) *corev1.Secret {
|
||||
if secret := volume.Secret; secret != nil {
|
||||
secretName := secret.SecretName
|
||||
secretFound, ok := secrets[secretName]
|
||||
if ok {
|
||||
return secretFound
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsVolumeUsingSecretProjection(volume corev1.Volume, secrets map[string]*corev1.Secret) *corev1.Secret {
|
||||
if volume.Projected != nil {
|
||||
for i := 0; i < len(volume.Projected.Sources); i++ {
|
||||
if secret := volume.Projected.Sources[i].Secret; secret != nil {
|
||||
secretName := secret.Name
|
||||
secretFound, ok := secrets[secretName]
|
||||
if ok {
|
||||
return secretFound
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -8,18 +8,28 @@ import (
|
||||
|
||||
func TestAreVolmesUsingSecrets(t *testing.T) {
|
||||
secretNamesToSearch := map[string]*corev1.Secret{
|
||||
"onepassword-database-secret": {},
|
||||
"onepassword-api-key": {},
|
||||
"onepassword-database-secret": {},
|
||||
"onepassword-api-key": {},
|
||||
"onepassword-app-token": {},
|
||||
"onepassword-user-credentials": {},
|
||||
}
|
||||
|
||||
volumeSecretNames := []string{
|
||||
"onepassword-database-secret",
|
||||
"onepassword-api-key",
|
||||
"some_other_key",
|
||||
}
|
||||
|
||||
volumes := generateVolumes(volumeSecretNames)
|
||||
|
||||
volumeProjectedSecretNames := []string{
|
||||
"onepassword-app-token",
|
||||
"onepassword-user-credentials",
|
||||
}
|
||||
|
||||
volumeProjected := generateVolumesProjected(volumeProjectedSecretNames)
|
||||
|
||||
volumes = append(volumes, volumeProjected)
|
||||
|
||||
if !AreVolumesUsingSecrets(volumes, secretNamesToSearch) {
|
||||
t.Errorf("Expected that volumes were using secrets but they were not detected.")
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/1Password/onepassword-operator/pkg/logs"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
@@ -54,7 +55,7 @@ func GetOperatorNamespace() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
ns := strings.TrimSpace(string(nsBytes))
|
||||
log.V(1).Info("Found namespace", "Namespace", ns)
|
||||
log.V(logs.DebugLevel).Info("Found namespace", "Namespace", ns)
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
|
202
vendor/cloud.google.com/go/compute/LICENSE
generated
vendored
202
vendor/cloud.google.com/go/compute/LICENSE
generated
vendored
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
542
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
542
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
@@ -1,542 +0,0 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://cloud.google.com/compute/docs/metadata/overview.
|
||||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254"
|
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST"
|
||||
|
||||
userAgent = "gcloud-golang/0.1"
|
||||
)
|
||||
|
||||
type cachedValue struct {
|
||||
k string
|
||||
trim bool
|
||||
mu sync.Mutex
|
||||
v string
|
||||
}
|
||||
|
||||
var (
|
||||
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var defaultClient = &Client{hc: newDefaultHTTPClient()}
|
||||
|
||||
func newDefaultHTTPClient() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string
|
||||
|
||||
func (suffix NotDefinedError) Error() string {
|
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
func (c *cachedValue) get(cl *Client) (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = cl.getTrimmed(c.k)
|
||||
} else {
|
||||
v, err = cl.Get(c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
onGCEOnce sync.Once
|
||||
onGCE bool
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||
func OnGCE() bool {
|
||||
onGCEOnce.Do(initOnGCE)
|
||||
return onGCE
|
||||
}
|
||||
|
||||
func initOnGCE() {
|
||||
onGCE = testOnGCE()
|
||||
}
|
||||
|
||||
func testOnGCE() bool {
|
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
resc := make(chan bool, 2)
|
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go func() {
|
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := newDefaultHTTPClient().Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
resolver := &net.Resolver{}
|
||||
addrs, err := resolver.LookupHost(ctx, "metadata.google.internal")
|
||||
if err != nil || len(addrs) == 0 {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
resc <- strsContains(addrs, metadataIP)
|
||||
}()
|
||||
|
||||
tryHarder := systemInfoSuggestsGCE()
|
||||
if tryHarder {
|
||||
res := <-resc
|
||||
if res {
|
||||
// The first strategy succeeded, so let's use it.
|
||||
return true
|
||||
}
|
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case res = <-resc:
|
||||
return res
|
||||
case <-timer.C:
|
||||
// Too slow. Who knows what this system is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc
|
||||
}
|
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
func systemInfoSuggestsGCE() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
// We don't have any non-Linux clues available, at least yet.
|
||||
return false
|
||||
}
|
||||
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
|
||||
name := strings.TrimSpace(string(slurp))
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
|
||||
// Subscribe calls Client.Subscribe on the default client.
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return defaultClient.Subscribe(suffix, fn)
|
||||
}
|
||||
|
||||
// Get calls Client.Get on the default client.
|
||||
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return defaultClient.ProjectID() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) { return defaultClient.InternalIP() }
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
|
||||
|
||||
// Email calls Client.Email on the default client.
|
||||
func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) }
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) { return defaultClient.Hostname() }
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) { return defaultClient.InstanceID() }
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) { return defaultClient.InstanceName() }
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) { return defaultClient.Zone() }
|
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributes on the default client.
|
||||
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
|
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributes on the default client.
|
||||
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
|
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValue(attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValue(attr)
|
||||
}
|
||||
|
||||
// Scopes calls Client.Scopes on the default client.
|
||||
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata.
|
||||
// Returns the client that uses the specified http.Client for HTTP requests.
|
||||
// If nil is specified, returns the default client.
|
||||
func NewClient(c *http.Client) *Client {
|
||||
if c == nil {
|
||||
return defaultClient
|
||||
}
|
||||
|
||||
return &Client{hc: c}
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(suffix string) (value, etag string, err error) {
|
||||
ctx := context.TODO()
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
suffix = strings.TrimLeft(suffix, "/")
|
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
var res *http.Response
|
||||
var reqErr error
|
||||
retryer := newRetryer()
|
||||
for {
|
||||
res, reqErr = c.hc.Do(req)
|
||||
var code int
|
||||
if res != nil {
|
||||
code = res.StatusCode
|
||||
}
|
||||
if delay, shouldRetry := retryer.Retry(code, reqErr); shouldRetry {
|
||||
if err := sleep(ctx, delay); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if reqErr != nil {
|
||||
return "", "", reqErr
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func (c *Client) Get(suffix string) (string, error) {
|
||||
val, _, err := c.getETag(suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (c *Client) getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = c.Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) lines(suffix string) ([]string, error) {
|
||||
j, err := c.Get(suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// Email returns the email address associated with the service account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Email(serviceAccount string) (string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) Hostname() (string, error) {
|
||||
return c.getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := c.Get("instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceName() (string, error) {
|
||||
return c.getTrimmed("instance/name")
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) Zone() (string, error) {
|
||||
zone, err := c.getTrimmed("instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
|
||||
return c.Get("instance/attributes/" + attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
|
||||
return c.Get("project/attributes/" + attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||
// is deleted. Subscribe returns the error value returned from the last call to
|
||||
// fn, which may be nil when ok == false.
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
const failedSubscribeSleep = time.Second * 5
|
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := c.getETag(suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(val, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok := true
|
||||
if strings.ContainsRune(suffix, '?') {
|
||||
suffix += "&wait_for_change=true&last_etag="
|
||||
} else {
|
||||
suffix += "?wait_for_change=true&last_etag="
|
||||
}
|
||||
for {
|
||||
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
|
||||
if err != nil {
|
||||
if _, deleted := err.(NotDefinedError); !deleted {
|
||||
time.Sleep(failedSubscribeSleep)
|
||||
continue // Retry on other errors.
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
lastETag = etag
|
||||
|
||||
if err := fn(val, ok); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
// Code is the HTTP response status code.
|
||||
Code int
|
||||
// Message is the server response message.
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
|
||||
}
|
114
vendor/cloud.google.com/go/compute/metadata/retry.go
generated
vendored
114
vendor/cloud.google.com/go/compute/metadata/retry.go
generated
vendored
@@ -1,114 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetryAttempts = 5
|
||||
)
|
||||
|
||||
var (
|
||||
syscallRetryable = func(err error) bool { return false }
|
||||
)
|
||||
|
||||
// defaultBackoff is basically equivalent to gax.Backoff without the need for
|
||||
// the dependency.
|
||||
type defaultBackoff struct {
|
||||
max time.Duration
|
||||
mul float64
|
||||
cur time.Duration
|
||||
}
|
||||
|
||||
func (b *defaultBackoff) Pause() time.Duration {
|
||||
d := time.Duration(1 + rand.Int63n(int64(b.cur)))
|
||||
b.cur = time.Duration(float64(b.cur) * b.mul)
|
||||
if b.cur > b.max {
|
||||
b.cur = b.max
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// sleep is the equivalent of gax.Sleep without the need for the dependency.
|
||||
func sleep(ctx context.Context, d time.Duration) error {
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return ctx.Err()
|
||||
case <-t.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func newRetryer() *metadataRetryer {
|
||||
return &metadataRetryer{bo: &defaultBackoff{
|
||||
cur: 100 * time.Millisecond,
|
||||
max: 30 * time.Second,
|
||||
mul: 2,
|
||||
}}
|
||||
}
|
||||
|
||||
type backoff interface {
|
||||
Pause() time.Duration
|
||||
}
|
||||
|
||||
type metadataRetryer struct {
|
||||
bo backoff
|
||||
attempts int
|
||||
}
|
||||
|
||||
func (r *metadataRetryer) Retry(status int, err error) (time.Duration, bool) {
|
||||
if status == http.StatusOK {
|
||||
return 0, false
|
||||
}
|
||||
retryOk := shouldRetry(status, err)
|
||||
if !retryOk {
|
||||
return 0, false
|
||||
}
|
||||
if r.attempts == maxRetryAttempts {
|
||||
return 0, false
|
||||
}
|
||||
r.attempts++
|
||||
return r.bo.Pause(), true
|
||||
}
|
||||
|
||||
func shouldRetry(status int, err error) bool {
|
||||
if 500 <= status && status <= 599 {
|
||||
return true
|
||||
}
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
return true
|
||||
}
|
||||
// Transient network errors should be retried.
|
||||
if syscallRetryable(err) {
|
||||
return true
|
||||
}
|
||||
if err, ok := err.(interface{ Temporary() bool }); ok {
|
||||
if err.Temporary() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if err, ok := err.(interface{ Unwrap() error }); ok {
|
||||
return shouldRetry(status, err.Unwrap())
|
||||
}
|
||||
return false
|
||||
}
|
26
vendor/cloud.google.com/go/compute/metadata/retry_linux.go
generated
vendored
26
vendor/cloud.google.com/go/compute/metadata/retry_linux.go
generated
vendored
@@ -1,26 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package metadata
|
||||
|
||||
import "syscall"
|
||||
|
||||
func init() {
|
||||
// Initialize syscallRetryable to return true on transient socket-level
|
||||
// errors. These errors are specific to Linux.
|
||||
syscallRetryable = func(err error) bool { return err == syscall.ECONNRESET || err == syscall.ECONNREFUSED }
|
||||
}
|
21
vendor/github.com/1Password/connect-sdk-go/LICENSE
generated
vendored
21
vendor/github.com/1Password/connect-sdk-go/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 1Password
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
860
vendor/github.com/1Password/connect-sdk-go/connect/client.go
generated
vendored
860
vendor/github.com/1Password/connect-sdk-go/connect/client.go
generated
vendored
@@ -1,860 +0,0 @@
|
||||
package connect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
jaegerClientConfig "github.com/uber/jaeger-client-go/config"
|
||||
"github.com/uber/jaeger-client-go/zipkin"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultUserAgent = "connect-sdk-go/%s"
|
||||
)
|
||||
|
||||
var (
|
||||
vaultUUIDError = fmt.Errorf("malformed vault uuid provided")
|
||||
itemUUIDError = fmt.Errorf("malformed item uuid provided")
|
||||
fileUUIDError = fmt.Errorf("malformed file uuid provided")
|
||||
)
|
||||
|
||||
// Client Represents an available 1Password Connect API to connect to
|
||||
type Client interface {
|
||||
GetVaults() ([]onepassword.Vault, error)
|
||||
GetVault(uuid string) (*onepassword.Vault, error)
|
||||
GetVaultByUUID(uuid string) (*onepassword.Vault, error)
|
||||
GetVaultByTitle(title string) (*onepassword.Vault, error)
|
||||
GetVaultsByTitle(uuid string) ([]onepassword.Vault, error)
|
||||
GetItems(vaultQuery string) ([]onepassword.Item, error)
|
||||
GetItem(itemQuery, vaultQuery string) (*onepassword.Item, error)
|
||||
GetItemByUUID(uuid string, vaultQuery string) (*onepassword.Item, error)
|
||||
GetItemByTitle(title string, vaultQuery string) (*onepassword.Item, error)
|
||||
GetItemsByTitle(title string, vaultQuery string) ([]onepassword.Item, error)
|
||||
CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error)
|
||||
UpdateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error)
|
||||
DeleteItem(item *onepassword.Item, vaultQuery string) error
|
||||
DeleteItemByID(itemUUID string, vaultQuery string) error
|
||||
DeleteItemByTitle(title string, vaultQuery string) error
|
||||
GetFiles(itemQuery string, vaultQuery string) ([]onepassword.File, error)
|
||||
GetFile(uuid string, itemQuery string, vaultQuery string) (*onepassword.File, error)
|
||||
GetFileContent(file *onepassword.File) ([]byte, error)
|
||||
DownloadFile(file *onepassword.File, targetDirectory string, overwrite bool) (string, error)
|
||||
LoadStructFromItemByUUID(config interface{}, itemUUID string, vaultQuery string) error
|
||||
LoadStructFromItemByTitle(config interface{}, itemTitle string, vaultQuery string) error
|
||||
LoadStructFromItem(config interface{}, itemQuery string, vaultQuery string) error
|
||||
LoadStruct(config interface{}) error
|
||||
}
|
||||
|
||||
type httpClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
const (
|
||||
envHostVariable = "OP_CONNECT_HOST"
|
||||
envTokenVariable = "OP_CONNECT_TOKEN"
|
||||
)
|
||||
|
||||
// NewClientFromEnvironment Returns a Secret Service client assuming that your
|
||||
// jwt is set in the OP_TOKEN environment variable
|
||||
func NewClientFromEnvironment() (Client, error) {
|
||||
host, found := os.LookupEnv(envHostVariable)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("There is no hostname available in the %q variable", envHostVariable)
|
||||
}
|
||||
|
||||
token, found := os.LookupEnv(envTokenVariable)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("There is no token available in the %q variable", envTokenVariable)
|
||||
}
|
||||
|
||||
return NewClient(host, token), nil
|
||||
}
|
||||
|
||||
// NewClient Returns a Secret Service client for a given url and jwt
|
||||
func NewClient(url string, token string) Client {
|
||||
return NewClientWithUserAgent(url, token, fmt.Sprintf(defaultUserAgent, SDKVersion))
|
||||
}
|
||||
|
||||
// NewClientWithUserAgent Returns a Secret Service client for a given url and jwt and identifies with userAgent
|
||||
func NewClientWithUserAgent(url string, token string, userAgent string) Client {
|
||||
if !opentracing.IsGlobalTracerRegistered() {
|
||||
cfg := jaegerClientConfig.Configuration{}
|
||||
zipkinPropagator := zipkin.NewZipkinB3HTTPHeaderPropagator()
|
||||
cfg.InitGlobalTracer(
|
||||
userAgent,
|
||||
jaegerClientConfig.Injector(opentracing.HTTPHeaders, zipkinPropagator),
|
||||
jaegerClientConfig.Extractor(opentracing.HTTPHeaders, zipkinPropagator),
|
||||
jaegerClientConfig.ZipkinSharedRPCSpan(true),
|
||||
)
|
||||
}
|
||||
|
||||
return &restClient{
|
||||
URL: url,
|
||||
Token: token,
|
||||
|
||||
userAgent: userAgent,
|
||||
tracer: opentracing.GlobalTracer(),
|
||||
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
}
|
||||
|
||||
type restClient struct {
|
||||
URL string
|
||||
Token string
|
||||
userAgent string
|
||||
tracer opentracing.Tracer
|
||||
client httpClient
|
||||
}
|
||||
|
||||
// GetVaults Get a list of all available vaults
|
||||
func (rs *restClient) GetVaults() ([]onepassword.Vault, error) {
|
||||
span := rs.tracer.StartSpan("GetVaults")
|
||||
defer span.Finish()
|
||||
|
||||
vaultURL := fmt.Sprintf("/v1/vaults")
|
||||
request, err := rs.buildRequest(http.MethodGet, vaultURL, http.NoBody, span)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := rs.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vaults []onepassword.Vault
|
||||
if err := parseResponse(response, http.StatusOK, &vaults); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vaults, nil
|
||||
}
|
||||
|
||||
// GetVault Get a vault based on its name or ID
|
||||
func (rs *restClient) GetVault(vaultQuery string) (*onepassword.Vault, error) {
|
||||
span := rs.tracer.StartSpan("GetVault")
|
||||
defer span.Finish()
|
||||
|
||||
if vaultQuery == "" {
|
||||
return nil, fmt.Errorf("Please provide either the vault name or its ID.")
|
||||
}
|
||||
if !isValidUUID(vaultQuery) {
|
||||
return rs.GetVaultByTitle(vaultQuery)
|
||||
}
|
||||
return rs.GetVaultByUUID(vaultQuery)
|
||||
}
|
||||
|
||||
func (rs *restClient) GetVaultByUUID(uuid string) (*onepassword.Vault, error) {
|
||||
if !isValidUUID(uuid) {
|
||||
return nil, vaultUUIDError
|
||||
}
|
||||
|
||||
span := rs.tracer.StartSpan("GetVaultByUUID")
|
||||
defer span.Finish()
|
||||
|
||||
vaultURL := fmt.Sprintf("/v1/vaults/%s", uuid)
|
||||
request, err := rs.buildRequest(http.MethodGet, vaultURL, http.NoBody, span)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := rs.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var vault onepassword.Vault
|
||||
if err := parseResponse(response, http.StatusOK, &vault); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vault, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) GetVaultByTitle(vaultName string) (*onepassword.Vault, error) {
|
||||
span := rs.tracer.StartSpan("GetVaultByTitle")
|
||||
defer span.Finish()
|
||||
|
||||
vaults, err := rs.GetVaultsByTitle(vaultName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(vaults) != 1 {
|
||||
return nil, fmt.Errorf("Found %d vaults with title %q", len(vaults), vaultName)
|
||||
}
|
||||
|
||||
return &vaults[0], 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
|
||||
}
|
||||
|
||||
var vaults []onepassword.Vault
|
||||
if err := parseResponse(response, http.StatusOK, &vaults); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vaults, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) getVaultUUID(vaultQuery string) (string, error) {
|
||||
if vaultQuery == "" {
|
||||
return "", fmt.Errorf("Please provide either the vault name or its ID.")
|
||||
}
|
||||
if isValidUUID(vaultQuery) {
|
||||
return vaultQuery, nil
|
||||
}
|
||||
vault, err := rs.GetVaultByTitle(vaultQuery)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return vault.ID, nil
|
||||
}
|
||||
|
||||
// GetItem Get a specific Item from the 1Password Connect API by either title or UUID
|
||||
func (rs *restClient) GetItem(itemQuery string, vaultQuery string) (*onepassword.Item, error) {
|
||||
span := rs.tracer.StartSpan("GetItem")
|
||||
defer span.Finish()
|
||||
|
||||
if itemQuery == "" {
|
||||
return nil, fmt.Errorf("Please provide either the item name or its ID.")
|
||||
}
|
||||
if !isValidUUID(itemQuery) {
|
||||
return rs.GetItemByTitle(itemQuery, vaultQuery)
|
||||
}
|
||||
return rs.GetItemByUUID(itemQuery, vaultQuery)
|
||||
}
|
||||
|
||||
// GetItemByUUID Get a specific Item from the 1Password Connect API by its UUID
|
||||
func (rs *restClient) GetItemByUUID(uuid string, vaultQuery string) (*onepassword.Item, error) {
|
||||
if !isValidUUID(uuid) {
|
||||
return nil, itemUUIDError
|
||||
}
|
||||
|
||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
span := rs.tracer.StartSpan("GetItemByUUID")
|
||||
defer span.Finish()
|
||||
|
||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", vaultUUID, uuid)
|
||||
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
|
||||
}
|
||||
var item onepassword.Item
|
||||
if err := parseResponse(response, http.StatusOK, &item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) GetItemByTitle(title string, vaultQuery string) (*onepassword.Item, error) {
|
||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 &items[0], nil
|
||||
}
|
||||
|
||||
func (rs *restClient) GetItemsByTitle(title string, vaultQuery string) ([]onepassword.Item, error) {
|
||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
var itemSummaries []onepassword.Item
|
||||
if err := parseResponse(response, http.StatusOK, &itemSummaries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]onepassword.Item, len(itemSummaries))
|
||||
for i, itemSummary := range itemSummaries {
|
||||
tempItem, err := rs.GetItem(itemSummary.ID, itemSummary.Vault.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items[i] = *tempItem
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) GetItems(vaultQuery string) ([]onepassword.Item, error) {
|
||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
span := rs.tracer.StartSpan("GetItems")
|
||||
defer span.Finish()
|
||||
|
||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items", vaultUUID)
|
||||
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
|
||||
}
|
||||
|
||||
var items []onepassword.Item
|
||||
if err := parseResponse(response, http.StatusOK, &items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) getItemUUID(itemQuery, vaultQuery string) (string, error) {
|
||||
if itemQuery == "" {
|
||||
return "", fmt.Errorf("Please provide either the item name or its ID.")
|
||||
}
|
||||
if isValidUUID(itemQuery) {
|
||||
return itemQuery, nil
|
||||
}
|
||||
item, err := rs.GetItemByTitle(itemQuery, vaultQuery)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return item.ID, nil
|
||||
}
|
||||
|
||||
// CreateItem Create a new item in a specified vault
|
||||
func (rs *restClient) CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
|
||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
span := rs.tracer.StartSpan("CreateItem")
|
||||
defer span.Finish()
|
||||
|
||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items", vaultUUID)
|
||||
itemBody, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request, err := rs.buildRequest(http.MethodPost, itemURL, bytes.NewBuffer(itemBody), span)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := rs.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var newItem onepassword.Item
|
||||
if err := parseResponse(response, http.StatusOK, &newItem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &newItem, nil
|
||||
}
|
||||
|
||||
// UpdateItem Update a new item in a specified vault
|
||||
func (rs *restClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) {
|
||||
span := rs.tracer.StartSpan("UpdateItem")
|
||||
defer span.Finish()
|
||||
|
||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", item.Vault.ID, item.ID)
|
||||
itemBody, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request, err := rs.buildRequest(http.MethodPut, itemURL, bytes.NewBuffer(itemBody), span)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := rs.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var newItem onepassword.Item
|
||||
if err := parseResponse(response, http.StatusOK, &newItem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &newItem, nil
|
||||
}
|
||||
|
||||
// DeleteItem Delete a new item in a specified vault
|
||||
func (rs *restClient) DeleteItem(item *onepassword.Item, vaultUUID string) error {
|
||||
span := rs.tracer.StartSpan("DeleteItem")
|
||||
defer span.Finish()
|
||||
|
||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", item.Vault.ID, item.ID)
|
||||
request, err := rs.buildRequest(http.MethodDelete, itemURL, http.NoBody, span)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := rs.client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := parseResponse(response, http.StatusNoContent, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteItemByID Delete a new item in a specified vault, specifying the item's uuid
|
||||
func (rs *restClient) DeleteItemByID(itemUUID string, vaultQuery string) error {
|
||||
if !isValidUUID(itemUUID) {
|
||||
return itemUUIDError
|
||||
}
|
||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
span := rs.tracer.StartSpan("DeleteItemByID")
|
||||
defer span.Finish()
|
||||
|
||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", vaultUUID, itemUUID)
|
||||
request, err := rs.buildRequest(http.MethodDelete, itemURL, http.NoBody, span)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := rs.client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := parseResponse(response, http.StatusNoContent, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteItemByTitle Delete a new item in a specified vault, specifying the item's title
|
||||
func (rs *restClient) DeleteItemByTitle(title string, vaultQuery string) error {
|
||||
span := rs.tracer.StartSpan("DeleteItemByTitle")
|
||||
defer span.Finish()
|
||||
|
||||
item, err := rs.GetItemByTitle(title, vaultQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return rs.DeleteItem(item, item.Vault.ID)
|
||||
}
|
||||
|
||||
func (rs *restClient) GetFiles(itemQuery string, vaultQuery string) ([]onepassword.File, error) {
|
||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
itemUUID, err := rs.getItemUUID(itemQuery, vaultQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
span := rs.tracer.StartSpan("GetFiles")
|
||||
defer span.Finish()
|
||||
|
||||
jsonURL := fmt.Sprintf("/v1/vaults/%s/items/%s/files", vaultUUID, itemUUID)
|
||||
request, err := rs.buildRequest(http.MethodGet, jsonURL, http.NoBody, span)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := rs.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var files []onepassword.File
|
||||
if err := parseResponse(response, http.StatusOK, &files); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// GetFile Get a specific File in a specified item.
|
||||
// This does not include the file contents. Call GetFileContent() to load the file's content.
|
||||
func (rs *restClient) GetFile(uuid string, itemQuery string, vaultQuery string) (*onepassword.File, error) {
|
||||
if !isValidUUID(uuid) {
|
||||
return nil, fileUUIDError
|
||||
}
|
||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
itemUUID, err := rs.getItemUUID(itemQuery, vaultQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
span := rs.tracer.StartSpan("GetFile")
|
||||
defer span.Finish()
|
||||
|
||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s", vaultUUID, itemUUID, uuid)
|
||||
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 err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var file onepassword.File
|
||||
if err := parseResponse(response, http.StatusOK, &file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
// GetFileContent retrieves the file's content.
|
||||
// If the file's content have previously been fetched, those contents are returned without making another request.
|
||||
func (rs *restClient) GetFileContent(file *onepassword.File) ([]byte, error) {
|
||||
if content, err := file.Content(); err == nil {
|
||||
return content, nil
|
||||
}
|
||||
response, err := rs.retrieveDocumentContent(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := readResponseBody(response, http.StatusOK)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file.SetContent(content)
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) DownloadFile(file *onepassword.File, targetDirectory string, overwriteIfExists bool) (string, error) {
|
||||
response, err := rs.retrieveDocumentContent(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
path := filepath.Join(targetDirectory, filepath.Base(file.Name))
|
||||
|
||||
var osFile *os.File
|
||||
|
||||
if overwriteIfExists {
|
||||
osFile, err = createFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
_, err = os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
osFile, err = createFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("a file already exists under the %s path. In order to overwrite it, set `overwriteIfExists` to true", path)
|
||||
}
|
||||
}
|
||||
defer osFile.Close()
|
||||
if _, err = io.Copy(osFile, response.Body); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) retrieveDocumentContent(file *onepassword.File) (*http.Response, error) {
|
||||
span := rs.tracer.StartSpan("GetFileContent")
|
||||
defer span.Finish()
|
||||
|
||||
request, err := rs.buildRequest(http.MethodGet, file.ContentPath, http.NoBody, span)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := rs.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func createFile(path string) (*os.File, error) {
|
||||
osFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = os.Chmod(path, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return osFile, nil
|
||||
}
|
||||
|
||||
func (rs *restClient) buildRequest(method string, path string, body io.Reader, span opentracing.Span) (*http.Request, error) {
|
||||
url := fmt.Sprintf("%s%s", rs.URL, path)
|
||||
|
||||
request, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rs.Token))
|
||||
request.Header.Set("User-Agent", rs.userAgent)
|
||||
|
||||
ext.SpanKindRPCClient.Set(span)
|
||||
ext.HTTPUrl.Set(span, path)
|
||||
ext.HTTPMethod.Set(span, method)
|
||||
|
||||
rs.tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(request.Header))
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func loadToStruct(item *parsedItem, config reflect.Value) error {
|
||||
t := config.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
value := config.Field(i)
|
||||
field := t.Field(i)
|
||||
|
||||
if !value.CanSet() {
|
||||
return fmt.Errorf("cannot load config into private fields")
|
||||
}
|
||||
|
||||
item.fields = append(item.fields, &field)
|
||||
item.values = append(item.values, &value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadStructFromItem Load configuration values based on struct tag from one 1P item.
|
||||
// It accepts as parameters item title/UUID and vault title/UUID.
|
||||
func (rs *restClient) LoadStructFromItem(i interface{}, itemQuery string, vaultQuery string) error {
|
||||
if itemQuery == "" {
|
||||
return fmt.Errorf("Please provide either the item name or its ID.")
|
||||
}
|
||||
if isValidUUID(itemQuery) {
|
||||
return rs.LoadStructFromItemByUUID(i, itemQuery, vaultQuery)
|
||||
}
|
||||
return rs.LoadStructFromItemByTitle(i, itemQuery, vaultQuery)
|
||||
}
|
||||
|
||||
// LoadStructFromItemByUUID Load configuration values based on struct tag from one 1P item.
|
||||
func (rs *restClient) LoadStructFromItemByUUID(i interface{}, itemUUID string, vaultQuery string) error {
|
||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isValidUUID(itemUUID) {
|
||||
return itemUUIDError
|
||||
}
|
||||
config, err := checkStruct(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
item := parsedItem{}
|
||||
item.itemUUID = itemUUID
|
||||
item.vaultUUID = vaultUUID
|
||||
|
||||
if err := loadToStruct(&item, config); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setValuesForTag(rs, &item, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadStructFromItemByTitle Load configuration values based on struct tag from one 1P item
|
||||
func (rs *restClient) LoadStructFromItemByTitle(i interface{}, itemTitle string, vaultQuery string) error {
|
||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := checkStruct(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
item := parsedItem{}
|
||||
item.itemTitle = itemTitle
|
||||
item.vaultUUID = vaultUUID
|
||||
|
||||
if err := loadToStruct(&item, config); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setValuesForTag(rs, &item, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadStruct Load configuration values based on struct tag
|
||||
func (rs *restClient) LoadStruct(i interface{}) error {
|
||||
config, err := checkStruct(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := config.Type()
|
||||
|
||||
// Multiple fields may be from a single item so we will collect them
|
||||
items := map[string]parsedItem{}
|
||||
|
||||
// Fetch the Vault from the environment
|
||||
vaultUUID, envVarFound := os.LookupEnv(envVaultVar)
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
value := config.Field(i)
|
||||
field := t.Field(i)
|
||||
tag := field.Tag.Get(itemTag)
|
||||
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !value.CanSet() {
|
||||
return fmt.Errorf("Cannot load config into private fields")
|
||||
}
|
||||
|
||||
itemVault, err := vaultUUIDForField(&field, vaultUUID, envVarFound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isValidUUID(itemVault) {
|
||||
return vaultUUIDError
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s/%s", itemVault, tag)
|
||||
parsed := items[key]
|
||||
parsed.vaultUUID = itemVault
|
||||
parsed.itemTitle = tag
|
||||
parsed.fields = append(parsed.fields, &field)
|
||||
parsed.values = append(parsed.values, &value)
|
||||
items[key] = parsed
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
if err := setValuesForTag(rs, &item, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseResponse(resp *http.Response, expectedStatusCode int, result interface{}) error {
|
||||
body, err := readResponseBody(resp, expectedStatusCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result != nil {
|
||||
if err := json.Unmarshal(body, result); err != nil {
|
||||
return fmt.Errorf("decoding response: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readResponseBody(resp *http.Response, expectedStatusCode int) ([]byte, error) {
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != expectedStatusCode {
|
||||
var errResp *onepassword.Error
|
||||
if err := json.Unmarshal(body, &errResp); err != nil {
|
||||
return nil, fmt.Errorf("decoding error response: %s", err)
|
||||
}
|
||||
return nil, errResp
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func isValidUUID(u string) bool {
|
||||
r := regexp.MustCompile("^[a-z0-9]{26}$")
|
||||
return r.MatchString(u)
|
||||
}
|
209
vendor/github.com/1Password/connect-sdk-go/connect/config_helper.go
generated
vendored
209
vendor/github.com/1Password/connect-sdk-go/connect/config_helper.go
generated
vendored
@@ -1,209 +0,0 @@
|
||||
package connect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Password/connect-sdk-go/onepassword"
|
||||
)
|
||||
|
||||
const (
|
||||
vaultTag = "opvault"
|
||||
itemTag = "opitem"
|
||||
sectionTag = "opsection"
|
||||
fieldTag = "opfield"
|
||||
urlTag = "opurl"
|
||||
|
||||
envVaultVar = "OP_VAULT"
|
||||
)
|
||||
|
||||
type parsedItem struct {
|
||||
vaultUUID string
|
||||
itemUUID string
|
||||
itemTitle string
|
||||
fields []*reflect.StructField
|
||||
values []*reflect.Value
|
||||
}
|
||||
|
||||
func checkStruct(i interface{}) (reflect.Value, error) {
|
||||
configP := reflect.ValueOf(i)
|
||||
if configP.Kind() != reflect.Ptr {
|
||||
return reflect.Value{}, fmt.Errorf("you must pass a pointer to Config struct")
|
||||
}
|
||||
|
||||
config := configP.Elem()
|
||||
if config.Kind() != reflect.Struct {
|
||||
return reflect.Value{}, fmt.Errorf("config values can only be loaded into a struct")
|
||||
}
|
||||
return config, nil
|
||||
|
||||
}
|
||||
func vaultUUIDForField(field *reflect.StructField, vaultUUID string, envVaultFound bool) (string, error) {
|
||||
// Check to see if a specific vault has been specified on the field
|
||||
// If the env vault id has not been found and item doesn't have a vault
|
||||
// return an error
|
||||
if vaultUUIDTag := field.Tag.Get(vaultTag); vaultUUIDTag == "" {
|
||||
if !envVaultFound {
|
||||
return "", fmt.Errorf("There is no vault for %q field", field.Name)
|
||||
}
|
||||
} else {
|
||||
return vaultUUIDTag, nil
|
||||
}
|
||||
|
||||
return vaultUUID, nil
|
||||
}
|
||||
|
||||
func setValuesForTag(client Client, parsedItem *parsedItem, byTitle bool) error {
|
||||
var item *onepassword.Item
|
||||
var err error
|
||||
if byTitle {
|
||||
item, err = client.GetItemByTitle(parsedItem.itemTitle, parsedItem.vaultUUID)
|
||||
} else {
|
||||
item, err = client.GetItem(parsedItem.itemUUID, parsedItem.vaultUUID)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, field := range parsedItem.fields {
|
||||
value := parsedItem.values[i]
|
||||
|
||||
if field.Type == reflect.TypeOf(onepassword.ItemURL{}) {
|
||||
url := &onepassword.ItemURL{
|
||||
Primary: urlPrimaryForName(field.Tag.Get(urlTag), item.URLs),
|
||||
Label: urlLabelForName(field.Tag.Get(urlTag), item.URLs),
|
||||
URL: urlURLForName(field.Tag.Get(urlTag), item.URLs),
|
||||
}
|
||||
value.Set(reflect.ValueOf(*url))
|
||||
continue
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s.%s", field.Tag.Get(sectionTag), field.Tag.Get(fieldTag))
|
||||
if path == "." {
|
||||
if field.Type == reflect.TypeOf(onepassword.Item{}) {
|
||||
value.Set(reflect.ValueOf(*item))
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("There is no %q specified for %q", fieldTag, field.Name)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, ".") {
|
||||
if field.Type == reflect.TypeOf(onepassword.ItemSection{}) {
|
||||
section := &onepassword.ItemSection{
|
||||
ID: sectionIDForName(field.Tag.Get(sectionTag), item.Sections),
|
||||
Label: sectionLabelForName(field.Tag.Get(sectionTag), item.Sections),
|
||||
}
|
||||
value.Set(reflect.ValueOf(*section))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
sectionID := sectionIDForName(field.Tag.Get(sectionTag), item.Sections)
|
||||
|
||||
for _, f := range item.Fields {
|
||||
fieldSectionID := ""
|
||||
if f.Section != nil {
|
||||
fieldSectionID = f.Section.ID
|
||||
}
|
||||
|
||||
if fieldSectionID == sectionID && f.Label == field.Tag.Get(fieldTag) {
|
||||
if err := setValue(value, f.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setValue(value *reflect.Value, toSet string) error {
|
||||
switch value.Kind() {
|
||||
case reflect.String:
|
||||
value.SetString(toSet)
|
||||
case reflect.Int:
|
||||
v, err := strconv.Atoi(toSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value.SetInt(int64(v))
|
||||
default:
|
||||
return fmt.Errorf("Unsupported type %q. Only string, int64, and onepassword.Item are supported", value.Kind())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sectionIDForName(name string, sections []*onepassword.ItemSection) string {
|
||||
if sections == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, s := range sections {
|
||||
if name == strings.ToLower(s.Label) {
|
||||
return s.ID
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func sectionLabelForName(name string, sections []*onepassword.ItemSection) string {
|
||||
if sections == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, s := range sections {
|
||||
if name == strings.ToLower(s.Label) {
|
||||
return s.Label
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func urlPrimaryForName(name string, itemURLs []onepassword.ItemURL) bool {
|
||||
if itemURLs == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, url := range itemURLs {
|
||||
if url.Label == strings.ToLower(name) {
|
||||
return url.Primary
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func urlLabelForName(name string, itemURLs []onepassword.ItemURL) string {
|
||||
if itemURLs == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, url := range itemURLs {
|
||||
if url.Label == strings.ToLower(name) {
|
||||
return url.Label
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func urlURLForName(name string, itemURLs []onepassword.ItemURL) string {
|
||||
if itemURLs == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, url := range itemURLs {
|
||||
if url.Label == strings.ToLower(name) {
|
||||
return url.URL
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
|
||||
}
|
104
vendor/github.com/1Password/connect-sdk-go/connect/version.go
generated
vendored
104
vendor/github.com/1Password/connect-sdk-go/connect/version.go
generated
vendored
@@ -1,104 +0,0 @@
|
||||
package connect
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SDKVersion is the latest Semantic Version of the library
|
||||
// Do not rename this variable without changing the regex in the Makefile
|
||||
const SDKVersion = "1.5.0"
|
||||
|
||||
const VersionHeaderKey = "1Password-Connect-Version"
|
||||
|
||||
// expectMinimumConnectVersion returns an error if the provided minimum version for Connect is lower than the version
|
||||
// reported in the response from Connect.
|
||||
func expectMinimumConnectVersion(resp *http.Response, minimumVersion version) error {
|
||||
serverVersion, err := getServerVersion(resp)
|
||||
if err != nil {
|
||||
// Return gracefully if server version cannot be determined reliably
|
||||
return nil
|
||||
}
|
||||
if !serverVersion.IsGreaterOrEqualThan(minimumVersion) {
|
||||
return fmt.Errorf("need at least version %s of Connect for this function, detected version %s. Please update your Connect server", minimumVersion, serverVersion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getServerVersion(resp *http.Response) (serverVersion, error) {
|
||||
versionHeader := resp.Header.Get(VersionHeaderKey)
|
||||
if versionHeader == "" {
|
||||
// The last version without the version header was v1.2.0
|
||||
return serverVersion{
|
||||
version: version{1, 2, 0},
|
||||
orEarlier: true,
|
||||
}, nil
|
||||
}
|
||||
return parseServerVersion(versionHeader)
|
||||
}
|
||||
|
||||
type version struct {
|
||||
major int
|
||||
minor int
|
||||
patch int
|
||||
}
|
||||
|
||||
// serverVersion describes the version reported by the server.
|
||||
type serverVersion struct {
|
||||
version
|
||||
// orEarlier is true if the version is derived from the lack of a version header from the server.
|
||||
orEarlier bool
|
||||
}
|
||||
|
||||
func (v version) String() string {
|
||||
return fmt.Sprintf("%d.%d.%d", v.major, v.minor, v.patch)
|
||||
}
|
||||
|
||||
func (v serverVersion) String() string {
|
||||
if v.orEarlier {
|
||||
return v.version.String() + " (or earlier)"
|
||||
}
|
||||
return v.version.String()
|
||||
}
|
||||
|
||||
// IsGreaterOrEqualThan returns true if the lefthand-side version is equal to or or a higher version than the provided
|
||||
// minimum according to the semantic versioning rules.
|
||||
func (v version) IsGreaterOrEqualThan(min version) bool {
|
||||
if v.major != min.major {
|
||||
// Different major version
|
||||
return v.major > min.major
|
||||
}
|
||||
|
||||
if v.minor != min.minor {
|
||||
// Same major, but different minor version
|
||||
return v.minor > min.minor
|
||||
}
|
||||
|
||||
// Same major and minor version
|
||||
return v.patch >= min.patch
|
||||
}
|
||||
|
||||
func parseServerVersion(v string) (serverVersion, error) {
|
||||
spl := strings.Split(v, ".")
|
||||
if len(spl) != 3 {
|
||||
return serverVersion{}, errors.New("wrong length")
|
||||
}
|
||||
var res [3]int
|
||||
for i := range res {
|
||||
tmp, err := strconv.Atoi(spl[i])
|
||||
if err != nil {
|
||||
return serverVersion{}, err
|
||||
}
|
||||
res[i] = tmp
|
||||
}
|
||||
return serverVersion{
|
||||
version: version{
|
||||
major: res[0],
|
||||
minor: res[1],
|
||||
patch: res[2],
|
||||
},
|
||||
}, nil
|
||||
}
|
21
vendor/github.com/1Password/connect-sdk-go/onepassword/errors.go
generated
vendored
21
vendor/github.com/1Password/connect-sdk-go/onepassword/errors.go
generated
vendored
@@ -1,21 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Error is an error returned by the Connect API.
|
||||
type Error struct {
|
||||
StatusCode int `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("status %d: %s", e.StatusCode, e.Message)
|
||||
}
|
||||
|
||||
func (e *Error) Is(target error) bool {
|
||||
t, ok := target.(*Error)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return t.Message == e.Message && t.StatusCode == e.StatusCode
|
||||
}
|
49
vendor/github.com/1Password/connect-sdk-go/onepassword/files.go
generated
vendored
49
vendor/github.com/1Password/connect-sdk-go/onepassword/files.go
generated
vendored
@@ -1,49 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Section *ItemSection `json:"section,omitempty"`
|
||||
Size int `json:"size"`
|
||||
ContentPath string `json:"content_path"`
|
||||
content []byte
|
||||
}
|
||||
|
||||
func (f *File) UnmarshalJSON(data []byte) error {
|
||||
var jsonFile struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Section *ItemSection `json:"section,omitempty"`
|
||||
Size int `json:"size"`
|
||||
ContentPath string `json:"content_path"`
|
||||
Content []byte `json:"content,omitempty"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &jsonFile); err != nil {
|
||||
return err
|
||||
}
|
||||
f.ID = jsonFile.ID
|
||||
f.Name = jsonFile.Name
|
||||
f.Section = jsonFile.Section
|
||||
f.Size = jsonFile.Size
|
||||
f.ContentPath = jsonFile.ContentPath
|
||||
f.content = jsonFile.Content
|
||||
return nil
|
||||
}
|
||||
|
||||
// Content returns the content of the file if they have been loaded and returns an error if they have not been loaded.
|
||||
// Use `client.GetFileContent(file *File)` instead to make sure the content is fetched automatically if not present.
|
||||
func (f *File) Content() ([]byte, error) {
|
||||
if f.content == nil {
|
||||
return nil, errors.New("file content not loaded")
|
||||
}
|
||||
return f.content, nil
|
||||
}
|
||||
|
||||
func (f *File) SetContent(content []byte) {
|
||||
f.content = content
|
||||
}
|
167
vendor/github.com/1Password/connect-sdk-go/onepassword/items.go
generated
vendored
167
vendor/github.com/1Password/connect-sdk-go/onepassword/items.go
generated
vendored
@@ -1,167 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ItemCategory Represents the template of the Item
|
||||
type ItemCategory string
|
||||
|
||||
const (
|
||||
Login ItemCategory = "LOGIN"
|
||||
Password ItemCategory = "PASSWORD"
|
||||
ApiCredential ItemCategory = "API_CREDENTIAL"
|
||||
Server ItemCategory = "SERVER"
|
||||
Database ItemCategory = "DATABASE"
|
||||
CreditCard ItemCategory = "CREDIT_CARD"
|
||||
Membership ItemCategory = "MEMBERSHIP"
|
||||
Passport ItemCategory = "PASSPORT"
|
||||
SoftwareLicense ItemCategory = "SOFTWARE_LICENSE"
|
||||
OutdoorLicense ItemCategory = "OUTDOOR_LICENSE"
|
||||
SecureNote ItemCategory = "SECURE_NOTE"
|
||||
WirelessRouter ItemCategory = "WIRELESS_ROUTER"
|
||||
BankAccount ItemCategory = "BANK_ACCOUNT"
|
||||
DriverLicense ItemCategory = "DRIVER_LICENSE"
|
||||
Identity ItemCategory = "IDENTITY"
|
||||
RewardProgram ItemCategory = "REWARD_PROGRAM"
|
||||
Document ItemCategory = "DOCUMENT"
|
||||
EmailAccount ItemCategory = "EMAIL_ACCOUNT"
|
||||
SocialSecurityNumber ItemCategory = "SOCIAL_SECURITY_NUMBER"
|
||||
MedicalRecord ItemCategory = "MEDICAL_RECORD"
|
||||
SSHKey ItemCategory = "SSH_KEY"
|
||||
Custom ItemCategory = "CUSTOM"
|
||||
)
|
||||
|
||||
// UnmarshalJSON Unmarshall Item Category enum strings to Go string enums
|
||||
func (ic *ItemCategory) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
json.Unmarshal(b, &s)
|
||||
category := ItemCategory(s)
|
||||
switch category {
|
||||
case Login, Password, Server, Database, CreditCard, Membership, Passport, SoftwareLicense,
|
||||
OutdoorLicense, SecureNote, WirelessRouter, BankAccount, DriverLicense, Identity, RewardProgram,
|
||||
Document, EmailAccount, SocialSecurityNumber, ApiCredential, MedicalRecord, SSHKey:
|
||||
*ic = category
|
||||
default:
|
||||
*ic = Custom
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Item represents an item returned to the consumer
|
||||
type Item struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
|
||||
URLs []ItemURL `json:"urls,omitempty"`
|
||||
Favorite bool `json:"favorite,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Version int `json:"version,omitempty"`
|
||||
|
||||
Vault ItemVault `json:"vault"`
|
||||
Category ItemCategory `json:"category,omitempty"` // TODO: switch this to `category`
|
||||
|
||||
Sections []*ItemSection `json:"sections,omitempty"`
|
||||
Fields []*ItemField `json:"fields,omitempty"`
|
||||
Files []*File `json:"files,omitempty"`
|
||||
|
||||
LastEditedBy string `json:"lastEditedBy,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
|
||||
// Deprecated: Connect does not return trashed items.
|
||||
Trashed bool `json:"trashed,omitempty"`
|
||||
}
|
||||
|
||||
// ItemVault represents the Vault the Item is found in
|
||||
type ItemVault struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// ItemURL is a simplified item URL
|
||||
type ItemURL struct {
|
||||
Primary bool `json:"primary,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
URL string `json:"href"`
|
||||
}
|
||||
|
||||
// ItemSection Representation of a Section on an item
|
||||
type ItemSection struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
}
|
||||
|
||||
// GeneratorRecipe Representation of a "recipe" used to generate a field
|
||||
type GeneratorRecipe struct {
|
||||
Length int `json:"length,omitempty"`
|
||||
CharacterSets []string `json:"characterSets,omitempty"`
|
||||
ExcludeCharacters string `json:"excludeCharacters,omitempty"`
|
||||
}
|
||||
|
||||
// ItemField Representation of a single field on an Item
|
||||
type ItemField struct {
|
||||
ID string `json:"id"`
|
||||
Section *ItemSection `json:"section,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Purpose string `json:"purpose,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Generate bool `json:"generate,omitempty"`
|
||||
Recipe *GeneratorRecipe `json:"recipe,omitempty"`
|
||||
Entropy float64 `json:"entropy,omitempty"`
|
||||
TOTP string `json:"totp,omitempty"`
|
||||
}
|
||||
|
||||
// GetValue 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 ""
|
||||
}
|
46
vendor/github.com/1Password/connect-sdk-go/onepassword/vaults.go
generated
vendored
46
vendor/github.com/1Password/connect-sdk-go/onepassword/vaults.go
generated
vendored
@@ -1,46 +0,0 @@
|
||||
package onepassword
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Vault represents a 1password Vault
|
||||
type Vault struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
AttrVersion int `json:"attributeVersion,omitempty"`
|
||||
ContentVersoin int `json:"contentVersion,omitempty"`
|
||||
Items int `json:"items,omitempty"`
|
||||
Type VaultType `json:"type,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
// VaultType Representation of what the Vault Type is
|
||||
type VaultType string
|
||||
|
||||
const (
|
||||
PersonalVault VaultType = "PERSONAL"
|
||||
EveryoneVault VaultType = "EVERYONE"
|
||||
TransferVault VaultType = "TRANSFER"
|
||||
UserCreatedVault VaultType = "USER_CREATED"
|
||||
UnknownVault VaultType = "UNKNOWN"
|
||||
)
|
||||
|
||||
// UnmarshalJSON Unmarshall Vault Type enum strings to Go string enums
|
||||
func (vt *VaultType) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
json.Unmarshal(b, &s)
|
||||
vaultType := VaultType(s)
|
||||
switch vaultType {
|
||||
case PersonalVault, EveryoneVault, TransferVault, UserCreatedVault:
|
||||
*vt = vaultType
|
||||
default:
|
||||
*vt = UnknownVault
|
||||
}
|
||||
return nil
|
||||
}
|
32
vendor/github.com/Azure/go-autorest/.gitignore
generated
vendored
32
vendor/github.com/Azure/go-autorest/.gitignore
generated
vendored
@@ -1,32 +0,0 @@
|
||||
# The standard Go .gitignore file follows. (Sourced from: github.com/github/gitignore/master/Go.gitignore)
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# go-autorest specific
|
||||
vendor/
|
||||
autorest/azure/example/example
|
1004
vendor/github.com/Azure/go-autorest/CHANGELOG.md
generated
vendored
1004
vendor/github.com/Azure/go-autorest/CHANGELOG.md
generated
vendored
File diff suppressed because it is too large
Load Diff
23
vendor/github.com/Azure/go-autorest/GNUmakefile
generated
vendored
23
vendor/github.com/Azure/go-autorest/GNUmakefile
generated
vendored
@@ -1,23 +0,0 @@
|
||||
DIR?=./autorest/
|
||||
|
||||
default: build
|
||||
|
||||
build: fmt
|
||||
go install $(DIR)
|
||||
|
||||
test:
|
||||
go test $(DIR) || exit 1
|
||||
|
||||
vet:
|
||||
@echo "go vet ."
|
||||
@go vet $(DIR)... ; if [ $$? -eq 1 ]; then \
|
||||
echo ""; \
|
||||
echo "Vet found suspicious constructs. Please check the reported constructs"; \
|
||||
echo "and fix them if necessary before submitting the code for review."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
fmt:
|
||||
gofmt -w $(DIR)
|
||||
|
||||
.PHONY: build test vet fmt
|
324
vendor/github.com/Azure/go-autorest/Gopkg.lock
generated
vendored
324
vendor/github.com/Azure/go-autorest/Gopkg.lock
generated
vendored
@@ -1,324 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:892e39e5c083d0943f1e80ab8351690f183c6a5ab24e1d280adcad424c26255e"
|
||||
name = "contrib.go.opencensus.io/exporter/ocagent"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a8a6f458bbc1d5042322ad1f9b65eeb0b69be9ea"
|
||||
version = "v0.6.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8f5acd4d4462b5136af644d25101f0968a7a94ee90fcb2059cec5b7cc42e0b20"
|
||||
name = "github.com/census-instrumentation/opencensus-proto"
|
||||
packages = [
|
||||
"gen-go/agent/common/v1",
|
||||
"gen-go/agent/metrics/v1",
|
||||
"gen-go/agent/trace/v1",
|
||||
"gen-go/metrics/v1",
|
||||
"gen-go/resource/v1",
|
||||
"gen-go/trace/v1",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d89fa54de508111353cb0b06403c00569be780d8"
|
||||
version = "v0.2.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "UT"
|
||||
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55"
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
|
||||
version = "v3.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cf0d2e435fd4ce45b789e93ef24b5f08e86be0e9807a16beb3694e2d8c9af965"
|
||||
name = "github.com/dimchansky/utfbom"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d2133a1ce379ef6fa992b0514a77146c60db9d1c"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:b7cb6054d3dff43b38ad2e92492f220f57ae6087ee797dca298139776749ace8"
|
||||
name = "github.com/golang/groupcache"
|
||||
packages = ["lru"]
|
||||
pruneopts = "UT"
|
||||
revision = "611e8accdfc92c4187d399e95ce826046d4c8d73"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e3839df32927e8d3403cd5aa7253d966e8ff80fc8f10e2e35d146461cd83fcfa"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"descriptor",
|
||||
"jsonpb",
|
||||
"proto",
|
||||
"protoc-gen-go/descriptor",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/struct",
|
||||
"ptypes/timestamp",
|
||||
"ptypes/wrappers",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"
|
||||
version = "v1.3.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c560cd79300fac84f124b96225181a637a70b60155919a3c36db50b7cca6b806"
|
||||
name = "github.com/grpc-ecosystem/grpc-gateway"
|
||||
packages = [
|
||||
"internal",
|
||||
"runtime",
|
||||
"utilities",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f7120437bb4f6c71f7f5076ad65a45310de2c009"
|
||||
version = "v1.12.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5d231480e1c64a726869bc4142d270184c419749d34f167646baa21008eb0a79"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "af06845cf3004701891bf4fdb884bfe4920b3727"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = "UT"
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:99d32780e5238c2621fff621123997c3e3cca96db8be13179013aea77dfab551"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = [
|
||||
"assert",
|
||||
"require",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "221dbe5ed46703ee255b1da0dec05086f5035f62"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7c5e00383399fe13de0b4b65c9fdde16275407ce8ac02d867eafeaa916edcc71"
|
||||
name = "go.opencensus.io"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
"internal/tagencoding",
|
||||
"metric/metricdata",
|
||||
"metric/metricproducer",
|
||||
"plugin/ocgrpc",
|
||||
"plugin/ochttp",
|
||||
"plugin/ochttp/propagation/b3",
|
||||
"plugin/ochttp/propagation/tracecontext",
|
||||
"resource",
|
||||
"stats",
|
||||
"stats/internal",
|
||||
"stats/view",
|
||||
"tag",
|
||||
"trace",
|
||||
"trace/internal",
|
||||
"trace/propagation",
|
||||
"trace/tracestate",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "aad2c527c5defcf89b5afab7f37274304195a6b2"
|
||||
version = "v0.22.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f604f5e2ee721b6757d962dfe7bab4f28aae50c456e39cfb2f3819762a44a6ae"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"pkcs12",
|
||||
"pkcs12/internal/rc2",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "e9b2fee46413994441b28dfca259d911d963dfed"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:334b27eac455cb6567ea28cd424230b07b1a64334a2f861a8075ac26ce10af43"
|
||||
name = "golang.org/x/lint"
|
||||
packages = [
|
||||
".",
|
||||
"golint",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "fdd1cda4f05fd1fd86124f0ef9ce31a0b72c8448"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:257a75d024975428ab9192bfc334c3490882f8cb21322ea5784ca8eca000a910"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"trace",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "1ddd1de85cb0337b623b740a609d35817d516a8d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:382bb5a7fb4034db3b6a2d19e5a4a6bcf52f4750530603c01ca18a172fa3089b"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["semaphore"]
|
||||
pruneopts = "UT"
|
||||
revision = "cd5d95a43a6e21273425c7ae415d3df9ea832eeb"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4da420ceda5f68e8d748aa2169d0ed44ffadb1bbd6537cf778a49563104189b8"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
pruneopts = "UT"
|
||||
revision = "ce4227a45e2eb77e5c847278dcc6a626742e2945"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8d8faad6b12a3a4c819a3f9618cb6ee1fa1cfc33253abeeea8b55336721e3405"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/language",
|
||||
"internal/language/compact",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
|
||||
version = "v0.3.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4eb5ea8395fb60212dd58b92c9db80bab59d5e99c7435f9a6a0a528c373b60e7"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"go/gcexportdata",
|
||||
"go/internal/gcimporter",
|
||||
"go/types/typeutil",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "259af5ff87bdcd4abf2ecda8edc3f13f04f26a42"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:964bb30febc27fabfbec4759fa530c6ec35e77a7c85fed90b9317ea39a054877"
|
||||
name = "google.golang.org/api"
|
||||
packages = ["support/bundler"]
|
||||
pruneopts = "UT"
|
||||
revision = "8a410c21381766a810817fd6200fce8838ecb277"
|
||||
version = "v0.14.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a8d5c2c6e746b3485e36908ab2a9e3d77b86b81f8156d88403c7d2b462431dfd"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = [
|
||||
"googleapis/api/httpbody",
|
||||
"googleapis/rpc/status",
|
||||
"protobuf/field_mask",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "51378566eb590fa106d1025ea12835a4416dda84"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b59ce3ddb11daeeccccc9cb3183b58ebf8e9a779f1c853308cd91612e817a301"
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
"backoff",
|
||||
"balancer",
|
||||
"balancer/base",
|
||||
"balancer/roundrobin",
|
||||
"binarylog/grpc_binarylog_v1",
|
||||
"codes",
|
||||
"connectivity",
|
||||
"credentials",
|
||||
"credentials/internal",
|
||||
"encoding",
|
||||
"encoding/proto",
|
||||
"grpclog",
|
||||
"internal",
|
||||
"internal/backoff",
|
||||
"internal/balancerload",
|
||||
"internal/binarylog",
|
||||
"internal/buffer",
|
||||
"internal/channelz",
|
||||
"internal/envconfig",
|
||||
"internal/grpcrand",
|
||||
"internal/grpcsync",
|
||||
"internal/resolver/dns",
|
||||
"internal/resolver/passthrough",
|
||||
"internal/syscall",
|
||||
"internal/transport",
|
||||
"keepalive",
|
||||
"metadata",
|
||||
"naming",
|
||||
"peer",
|
||||
"resolver",
|
||||
"serviceconfig",
|
||||
"stats",
|
||||
"status",
|
||||
"tap",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "1a3960e4bd028ac0cec0a2afd27d7d8e67c11514"
|
||||
version = "v1.25.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b75b3deb2bce8bc079e16bb2aecfe01eb80098f5650f9e93e5643ca8b7b73737"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "1f64d6156d11335c3f22d9330b0ad14fc1e789ce"
|
||||
version = "v2.2.7"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"contrib.go.opencensus.io/exporter/ocagent",
|
||||
"github.com/dgrijalva/jwt-go",
|
||||
"github.com/dimchansky/utfbom",
|
||||
"github.com/mitchellh/go-homedir",
|
||||
"github.com/stretchr/testify/require",
|
||||
"go.opencensus.io/plugin/ochttp",
|
||||
"go.opencensus.io/plugin/ochttp/propagation/tracecontext",
|
||||
"go.opencensus.io/stats/view",
|
||||
"go.opencensus.io/trace",
|
||||
"golang.org/x/crypto/pkcs12",
|
||||
"golang.org/x/lint/golint",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
59
vendor/github.com/Azure/go-autorest/Gopkg.toml
generated
vendored
59
vendor/github.com/Azure/go-autorest/Gopkg.toml
generated
vendored
@@ -1,59 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
required = ["golang.org/x/lint/golint"]
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "contrib.go.opencensus.io/exporter/ocagent"
|
||||
version = "0.6.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
version = "3.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/dimchansky/utfbom"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.3.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "go.opencensus.io"
|
||||
version = "0.22.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
191
vendor/github.com/Azure/go-autorest/LICENSE
generated
vendored
191
vendor/github.com/Azure/go-autorest/LICENSE
generated
vendored
@@ -1,191 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2015 Microsoft Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
165
vendor/github.com/Azure/go-autorest/README.md
generated
vendored
165
vendor/github.com/Azure/go-autorest/README.md
generated
vendored
@@ -1,165 +0,0 @@
|
||||
# go-autorest
|
||||
|
||||
[](https://godoc.org/github.com/Azure/go-autorest/autorest)
|
||||
[](https://dev.azure.com/azure-sdk/public/_build/latest?definitionId=625&branchName=master)
|
||||
[](https://goreportcard.com/report/Azure/go-autorest)
|
||||
|
||||
Package go-autorest provides an HTTP request client for use with [Autorest](https://github.com/Azure/autorest.go)-generated API client packages.
|
||||
|
||||
An authentication client tested with Azure Active Directory (AAD) is also
|
||||
provided in this repo in the package
|
||||
`github.com/Azure/go-autorest/autorest/adal`. Despite its name, this package
|
||||
is maintained only as part of the Azure Go SDK and is not related to other
|
||||
"ADAL" libraries in [github.com/AzureAD](https://github.com/AzureAD).
|
||||
|
||||
## Overview
|
||||
|
||||
Package go-autorest implements an HTTP request pipeline suitable for use across
|
||||
multiple goroutines and provides the shared routines used by packages generated
|
||||
by [Autorest](https://github.com/Azure/autorest.go).
|
||||
|
||||
The package breaks sending and responding to HTTP requests into three phases: Preparing, Sending,
|
||||
and Responding. A typical pattern is:
|
||||
|
||||
```go
|
||||
req, err := Prepare(&http.Request{},
|
||||
token.WithAuthorization())
|
||||
|
||||
resp, err := Send(req,
|
||||
WithLogging(logger),
|
||||
DoErrorIfStatusCode(http.StatusInternalServerError),
|
||||
DoCloseIfError(),
|
||||
DoRetryForAttempts(5, time.Second))
|
||||
|
||||
err = Respond(resp,
|
||||
ByDiscardingBody(),
|
||||
ByClosing())
|
||||
```
|
||||
|
||||
Each phase relies on decorators to modify and / or manage processing. Decorators may first modify
|
||||
and then pass the data along, pass the data first and then modify the result, or wrap themselves
|
||||
around passing the data (such as a logger might do). Decorators run in the order provided. For
|
||||
example, the following:
|
||||
|
||||
```go
|
||||
req, err := Prepare(&http.Request{},
|
||||
WithBaseURL("https://microsoft.com/"),
|
||||
WithPath("a"),
|
||||
WithPath("b"),
|
||||
WithPath("c"))
|
||||
```
|
||||
|
||||
will set the URL to:
|
||||
|
||||
```
|
||||
https://microsoft.com/a/b/c
|
||||
```
|
||||
|
||||
Preparers and Responders may be shared and re-used (assuming the underlying decorators support
|
||||
sharing and re-use). Performant use is obtained by creating one or more Preparers and Responders
|
||||
shared among multiple go-routines, and a single Sender shared among multiple sending go-routines,
|
||||
all bound together by means of input / output channels.
|
||||
|
||||
Decorators hold their passed state within a closure (such as the path components in the example
|
||||
above). Be careful to share Preparers and Responders only in a context where such held state
|
||||
applies. For example, it may not make sense to share a Preparer that applies a query string from a
|
||||
fixed set of values. Similarly, sharing a Responder that reads the response body into a passed
|
||||
struct (e.g., `ByUnmarshallingJson`) is likely incorrect.
|
||||
|
||||
Errors raised by autorest objects and methods will conform to the `autorest.Error` interface.
|
||||
|
||||
See the included examples for more detail. For details on the suggested use of this package by
|
||||
generated clients, see the Client described below.
|
||||
|
||||
## Helpers
|
||||
|
||||
### Handling Swagger Dates
|
||||
|
||||
The Swagger specification (https://swagger.io) that drives AutoRest
|
||||
(https://github.com/Azure/autorest/) precisely defines two date forms: date and date-time. The
|
||||
github.com/Azure/go-autorest/autorest/date package provides time.Time derivations to ensure correct
|
||||
parsing and formatting.
|
||||
|
||||
### Handling Empty Values
|
||||
|
||||
In JSON, missing values have different semantics than empty values. This is especially true for
|
||||
services using the HTTP PATCH verb. The JSON submitted with a PATCH request generally contains
|
||||
only those values to modify. Missing values are to be left unchanged. Developers, then, require a
|
||||
means to both specify an empty value and to leave the value out of the submitted JSON.
|
||||
|
||||
The Go JSON package (`encoding/json`) supports the `omitempty` tag. When specified, it omits
|
||||
empty values from the rendered JSON. Since Go defines default values for all base types (such as ""
|
||||
for string and 0 for int) and provides no means to mark a value as actually empty, the JSON package
|
||||
treats default values as meaning empty, omitting them from the rendered JSON. This means that, using
|
||||
the Go base types encoded through the default JSON package, it is not possible to create JSON to
|
||||
clear a value at the server.
|
||||
|
||||
The workaround within the Go community is to use pointers to base types in lieu of base types within
|
||||
structures that map to JSON. For example, instead of a value of type `string`, the workaround uses
|
||||
`*string`. While this enables distinguishing empty values from those to be unchanged, creating
|
||||
pointers to a base type (notably constant, in-line values) requires additional variables. This, for
|
||||
example,
|
||||
|
||||
```go
|
||||
s := struct {
|
||||
S *string
|
||||
}{ S: &"foo" }
|
||||
```
|
||||
fails, while, this
|
||||
|
||||
```go
|
||||
v := "foo"
|
||||
s := struct {
|
||||
S *string
|
||||
}{ S: &v }
|
||||
```
|
||||
succeeds.
|
||||
|
||||
To ease using pointers, the subpackage `to` contains helpers that convert to and from pointers for
|
||||
Go base types which have Swagger analogs. It also provides a helper that converts between
|
||||
`map[string]string` and `map[string]*string`, enabling the JSON to specify that the value
|
||||
associated with a key should be cleared. With the helpers, the previous example becomes
|
||||
|
||||
```go
|
||||
s := struct {
|
||||
S *string
|
||||
}{ S: to.StringPtr("foo") }
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/Azure/go-autorest/autorest
|
||||
go get github.com/Azure/go-autorest/autorest/azure
|
||||
go get github.com/Azure/go-autorest/autorest/date
|
||||
go get github.com/Azure/go-autorest/autorest/to
|
||||
```
|
||||
|
||||
### Using with Go Modules
|
||||
In [v12.0.1](https://github.com/Azure/go-autorest/pull/386), this repository introduced the following modules.
|
||||
|
||||
- autorest/adal
|
||||
- autorest/azure/auth
|
||||
- autorest/azure/cli
|
||||
- autorest/date
|
||||
- autorest/mocks
|
||||
- autorest/to
|
||||
- autorest/validation
|
||||
- autorest
|
||||
- logger
|
||||
- tracing
|
||||
|
||||
Tagging cumulative SDK releases as a whole (e.g. `v12.3.0`) is still enabled to support consumers of this repo that have not yet migrated to modules.
|
||||
|
||||
## License
|
||||
|
||||
See LICENSE file.
|
||||
|
||||
-----
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of
|
||||
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
|
||||
see the [Code of Conduct
|
||||
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
||||
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
|
||||
questions or comments.
|
191
vendor/github.com/Azure/go-autorest/autorest/LICENSE
generated
vendored
191
vendor/github.com/Azure/go-autorest/autorest/LICENSE
generated
vendored
@@ -1,191 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2015 Microsoft Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
191
vendor/github.com/Azure/go-autorest/autorest/adal/LICENSE
generated
vendored
191
vendor/github.com/Azure/go-autorest/autorest/adal/LICENSE
generated
vendored
@@ -1,191 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2015 Microsoft Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
294
vendor/github.com/Azure/go-autorest/autorest/adal/README.md
generated
vendored
294
vendor/github.com/Azure/go-autorest/autorest/adal/README.md
generated
vendored
@@ -1,294 +0,0 @@
|
||||
# NOTE: This module will go out of support by March 31, 2023. For authenticating with Azure AD, use module [azidentity](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity) instead. For help migrating from `adal` to `azidentiy` please consult the [migration guide](https://aka.ms/azsdk/go/identity/migration). General information about the retirement of this and other legacy modules can be found [here](https://azure.microsoft.com/updates/support-for-azure-sdk-libraries-that-do-not-conform-to-our-current-azure-sdk-guidelines-will-be-retired-as-of-31-march-2023/).
|
||||
|
||||
# Azure Active Directory authentication for Go
|
||||
|
||||
This is a standalone package for authenticating with Azure Active
|
||||
Directory from other Go libraries and applications, in particular the [Azure SDK
|
||||
for Go](https://github.com/Azure/azure-sdk-for-go).
|
||||
|
||||
Note: Despite the package's name it is not related to other "ADAL" libraries
|
||||
maintained in the [github.com/AzureAD](https://github.com/AzureAD) org. Issues
|
||||
should be opened in [this repo's](https://github.com/Azure/go-autorest/issues)
|
||||
or [the SDK's](https://github.com/Azure/azure-sdk-for-go/issues) issue
|
||||
trackers.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get -u github.com/Azure/go-autorest/autorest/adal
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
An Active Directory application is required in order to use this library. An application can be registered in the [Azure Portal](https://portal.azure.com/) by following these [guidelines](https://docs.microsoft.com/azure/active-directory/develop/active-directory-integrating-applications) or using the [Azure CLI](https://github.com/Azure/azure-cli).
|
||||
|
||||
### Register an Azure AD Application with secret
|
||||
|
||||
|
||||
1. Register a new application with a `secret` credential
|
||||
|
||||
```
|
||||
az ad app create \
|
||||
--display-name example-app \
|
||||
--homepage https://example-app/home \
|
||||
--identifier-uris https://example-app/app \
|
||||
--password secret
|
||||
```
|
||||
|
||||
2. Create a service principal using the `Application ID` from previous step
|
||||
|
||||
```
|
||||
az ad sp create --id "Application ID"
|
||||
```
|
||||
|
||||
* Replace `Application ID` with `appId` from step 1.
|
||||
|
||||
### Register an Azure AD Application with certificate
|
||||
|
||||
1. Create a private key
|
||||
|
||||
```
|
||||
openssl genrsa -out "example-app.key" 2048
|
||||
```
|
||||
|
||||
2. Create the certificate
|
||||
|
||||
```
|
||||
openssl req -new -key "example-app.key" -subj "/CN=example-app" -out "example-app.csr"
|
||||
openssl x509 -req -in "example-app.csr" -signkey "example-app.key" -out "example-app.crt" -days 10000
|
||||
```
|
||||
|
||||
3. Create the PKCS12 version of the certificate containing also the private key
|
||||
|
||||
```
|
||||
openssl pkcs12 -export -out "example-app.pfx" -inkey "example-app.key" -in "example-app.crt" -passout pass:
|
||||
|
||||
```
|
||||
|
||||
4. Register a new application with the certificate content form `example-app.crt`
|
||||
|
||||
```
|
||||
certificateContents="$(tail -n+2 "example-app.crt" | head -n-1)"
|
||||
|
||||
az ad app create \
|
||||
--display-name example-app \
|
||||
--homepage https://example-app/home \
|
||||
--identifier-uris https://example-app/app \
|
||||
--key-usage Verify --end-date 2018-01-01 \
|
||||
--key-value "${certificateContents}"
|
||||
```
|
||||
|
||||
5. Create a service principal using the `Application ID` from previous step
|
||||
|
||||
```
|
||||
az ad sp create --id "APPLICATION_ID"
|
||||
```
|
||||
|
||||
* Replace `APPLICATION_ID` with `appId` from step 4.
|
||||
|
||||
|
||||
### Grant the necessary permissions
|
||||
|
||||
Azure relies on a Role-Based Access Control (RBAC) model to manage the access to resources at a fine-grained
|
||||
level. There is a set of [pre-defined roles](https://docs.microsoft.com/azure/active-directory/role-based-access-built-in-roles)
|
||||
which can be assigned to a service principal of an Azure AD application depending of your needs.
|
||||
|
||||
```
|
||||
az role assignment create --assigner "SERVICE_PRINCIPAL_ID" --role "ROLE_NAME"
|
||||
```
|
||||
|
||||
* Replace the `SERVICE_PRINCIPAL_ID` with the `appId` from previous step.
|
||||
* Replace the `ROLE_NAME` with a role name of your choice.
|
||||
|
||||
It is also possible to define custom role definitions.
|
||||
|
||||
```
|
||||
az role definition create --role-definition role-definition.json
|
||||
```
|
||||
|
||||
* Check [custom roles](https://docs.microsoft.com/azure/active-directory/role-based-access-control-custom-roles) for more details regarding the content of `role-definition.json` file.
|
||||
|
||||
|
||||
### Acquire Access Token
|
||||
|
||||
The common configuration used by all flows:
|
||||
|
||||
```Go
|
||||
const activeDirectoryEndpoint = "https://login.microsoftonline.com/"
|
||||
tenantID := "TENANT_ID"
|
||||
oauthConfig, err := adal.NewOAuthConfig(activeDirectoryEndpoint, tenantID)
|
||||
|
||||
applicationID := "APPLICATION_ID"
|
||||
|
||||
callback := func(token adal.Token) error {
|
||||
// This is called after the token is acquired
|
||||
}
|
||||
|
||||
// The resource for which the token is acquired
|
||||
resource := "https://management.core.windows.net/"
|
||||
```
|
||||
|
||||
* Replace the `TENANT_ID` with your tenant ID.
|
||||
* Replace the `APPLICATION_ID` with the value from previous section.
|
||||
|
||||
#### Client Credentials
|
||||
|
||||
```Go
|
||||
applicationSecret := "APPLICATION_SECRET"
|
||||
|
||||
spt, err := adal.NewServicePrincipalToken(
|
||||
*oauthConfig,
|
||||
appliationID,
|
||||
applicationSecret,
|
||||
resource,
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Acquire a new access token
|
||||
err = spt.Refresh()
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
* Replace the `APPLICATION_SECRET` with the `password` value from previous section.
|
||||
|
||||
#### Client Certificate
|
||||
|
||||
```Go
|
||||
certificatePath := "./example-app.pfx"
|
||||
|
||||
certData, err := ioutil.ReadFile(certificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", certificatePath, err)
|
||||
}
|
||||
|
||||
// Get the certificate and private key from pfx file
|
||||
certificate, rsaPrivateKey, err := decodePkcs12(certData, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromCertificate(
|
||||
*oauthConfig,
|
||||
applicationID,
|
||||
certificate,
|
||||
rsaPrivateKey,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
// Acquire a new access token
|
||||
err = spt.Refresh()
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
* Update the certificate path to point to the example-app.pfx file which was created in previous section.
|
||||
|
||||
|
||||
#### Device Code
|
||||
|
||||
```Go
|
||||
oauthClient := &http.Client{}
|
||||
|
||||
// Acquire the device code
|
||||
deviceCode, err := adal.InitiateDeviceAuth(
|
||||
oauthClient,
|
||||
*oauthConfig,
|
||||
applicationID,
|
||||
resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to start device auth flow: %s", err)
|
||||
}
|
||||
|
||||
// Display the authentication message
|
||||
fmt.Println(*deviceCode.Message)
|
||||
|
||||
// Wait here until the user is authenticated
|
||||
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to finish device auth flow: %s", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromManualToken(
|
||||
*oauthConfig,
|
||||
applicationID,
|
||||
resource,
|
||||
*token,
|
||||
callbacks...)
|
||||
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
#### Username password authenticate
|
||||
|
||||
```Go
|
||||
spt, err := adal.NewServicePrincipalTokenFromUsernamePassword(
|
||||
*oauthConfig,
|
||||
applicationID,
|
||||
username,
|
||||
password,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
#### Authorization code authenticate
|
||||
|
||||
``` Go
|
||||
spt, err := adal.NewServicePrincipalTokenFromAuthorizationCode(
|
||||
*oauthConfig,
|
||||
applicationID,
|
||||
clientSecret,
|
||||
authorizationCode,
|
||||
redirectURI,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
err = spt.Refresh()
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
### Command Line Tool
|
||||
|
||||
A command line tool is available in `cmd/adal.go` that can acquire a token for a given resource. It supports all flows mentioned above.
|
||||
|
||||
```
|
||||
adal -h
|
||||
|
||||
Usage of ./adal:
|
||||
-applicationId string
|
||||
application id
|
||||
-certificatePath string
|
||||
path to pk12/PFC application certificate
|
||||
-mode string
|
||||
authentication mode (device, secret, cert, refresh) (default "device")
|
||||
-resource string
|
||||
resource for which the token is requested
|
||||
-secret string
|
||||
application secret
|
||||
-tenantId string
|
||||
tenant id
|
||||
-tokenCachePath string
|
||||
location of oath token cache (default "/home/cgc/.adal/accessToken.json")
|
||||
```
|
||||
|
||||
Example acquire a token for `https://management.core.windows.net/` using device code flow:
|
||||
|
||||
```
|
||||
adal -mode device \
|
||||
-applicationId "APPLICATION_ID" \
|
||||
-tenantId "TENANT_ID" \
|
||||
-resource https://management.core.windows.net/
|
||||
|
||||
```
|
151
vendor/github.com/Azure/go-autorest/autorest/adal/config.go
generated
vendored
151
vendor/github.com/Azure/go-autorest/autorest/adal/config.go
generated
vendored
@@ -1,151 +0,0 @@
|
||||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
activeDirectoryEndpointTemplate = "%s/oauth2/%s%s"
|
||||
)
|
||||
|
||||
// OAuthConfig represents the endpoints needed
|
||||
// in OAuth operations
|
||||
type OAuthConfig struct {
|
||||
AuthorityEndpoint url.URL `json:"authorityEndpoint"`
|
||||
AuthorizeEndpoint url.URL `json:"authorizeEndpoint"`
|
||||
TokenEndpoint url.URL `json:"tokenEndpoint"`
|
||||
DeviceCodeEndpoint url.URL `json:"deviceCodeEndpoint"`
|
||||
}
|
||||
|
||||
// IsZero returns true if the OAuthConfig object is zero-initialized.
|
||||
func (oac OAuthConfig) IsZero() bool {
|
||||
return oac == OAuthConfig{}
|
||||
}
|
||||
|
||||
func validateStringParam(param, name string) error {
|
||||
if len(param) == 0 {
|
||||
return fmt.Errorf("parameter '" + name + "' cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewOAuthConfig returns an OAuthConfig with tenant specific urls
|
||||
func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) {
|
||||
apiVer := "1.0"
|
||||
return NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, tenantID, &apiVer)
|
||||
}
|
||||
|
||||
// NewOAuthConfigWithAPIVersion returns an OAuthConfig with tenant specific urls.
|
||||
// If apiVersion is not nil the "api-version" query parameter will be appended to the endpoint URLs with the specified value.
|
||||
func NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, tenantID string, apiVersion *string) (*OAuthConfig, error) {
|
||||
if err := validateStringParam(activeDirectoryEndpoint, "activeDirectoryEndpoint"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
api := ""
|
||||
// it's legal for tenantID to be empty so don't validate it
|
||||
if apiVersion != nil {
|
||||
if err := validateStringParam(*apiVersion, "apiVersion"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
api = fmt.Sprintf("?api-version=%s", *apiVersion)
|
||||
}
|
||||
u, err := url.Parse(activeDirectoryEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorityURL, err := u.Parse(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorizeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "authorize", api))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "token", api))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deviceCodeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "devicecode", api))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &OAuthConfig{
|
||||
AuthorityEndpoint: *authorityURL,
|
||||
AuthorizeEndpoint: *authorizeURL,
|
||||
TokenEndpoint: *tokenURL,
|
||||
DeviceCodeEndpoint: *deviceCodeURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MultiTenantOAuthConfig provides endpoints for primary and aulixiary tenant IDs.
|
||||
type MultiTenantOAuthConfig interface {
|
||||
PrimaryTenant() *OAuthConfig
|
||||
AuxiliaryTenants() []*OAuthConfig
|
||||
}
|
||||
|
||||
// OAuthOptions contains optional OAuthConfig creation arguments.
|
||||
type OAuthOptions struct {
|
||||
APIVersion string
|
||||
}
|
||||
|
||||
func (c OAuthOptions) apiVersion() string {
|
||||
if c.APIVersion != "" {
|
||||
return fmt.Sprintf("?api-version=%s", c.APIVersion)
|
||||
}
|
||||
return "1.0"
|
||||
}
|
||||
|
||||
// NewMultiTenantOAuthConfig creates an object that support multitenant OAuth configuration.
|
||||
// See https://docs.microsoft.com/en-us/azure/azure-resource-manager/authenticate-multi-tenant for more information.
|
||||
func NewMultiTenantOAuthConfig(activeDirectoryEndpoint, primaryTenantID string, auxiliaryTenantIDs []string, options OAuthOptions) (MultiTenantOAuthConfig, error) {
|
||||
if len(auxiliaryTenantIDs) == 0 || len(auxiliaryTenantIDs) > 3 {
|
||||
return nil, errors.New("must specify one to three auxiliary tenants")
|
||||
}
|
||||
mtCfg := multiTenantOAuthConfig{
|
||||
cfgs: make([]*OAuthConfig, len(auxiliaryTenantIDs)+1),
|
||||
}
|
||||
apiVer := options.apiVersion()
|
||||
pri, err := NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, primaryTenantID, &apiVer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create OAuthConfig for primary tenant: %v", err)
|
||||
}
|
||||
mtCfg.cfgs[0] = pri
|
||||
for i := range auxiliaryTenantIDs {
|
||||
aux, err := NewOAuthConfig(activeDirectoryEndpoint, auxiliaryTenantIDs[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create OAuthConfig for tenant '%s': %v", auxiliaryTenantIDs[i], err)
|
||||
}
|
||||
mtCfg.cfgs[i+1] = aux
|
||||
}
|
||||
return mtCfg, nil
|
||||
}
|
||||
|
||||
type multiTenantOAuthConfig struct {
|
||||
// first config in the slice is the primary tenant
|
||||
cfgs []*OAuthConfig
|
||||
}
|
||||
|
||||
func (m multiTenantOAuthConfig) PrimaryTenant() *OAuthConfig {
|
||||
return m.cfgs[0]
|
||||
}
|
||||
|
||||
func (m multiTenantOAuthConfig) AuxiliaryTenants() []*OAuthConfig {
|
||||
return m.cfgs[1:]
|
||||
}
|
273
vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go
generated
vendored
273
vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go
generated
vendored
@@ -1,273 +0,0 @@
|
||||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
This file is largely based on rjw57/oauth2device's code, with the follow differences:
|
||||
* scope -> resource, and only allow a single one
|
||||
* receive "Message" in the DeviceCode struct and show it to users as the prompt
|
||||
* azure-xplat-cli has the following behavior that this emulates:
|
||||
- does not send client_secret during the token exchange
|
||||
- sends resource again in the token exchange request
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
logPrefix = "autorest/adal/devicetoken:"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDeviceGeneric represents an unknown error from the token endpoint when using device flow
|
||||
ErrDeviceGeneric = fmt.Errorf("%s Error while retrieving OAuth token: Unknown Error", logPrefix)
|
||||
|
||||
// ErrDeviceAccessDenied represents an access denied error from the token endpoint when using device flow
|
||||
ErrDeviceAccessDenied = fmt.Errorf("%s Error while retrieving OAuth token: Access Denied", logPrefix)
|
||||
|
||||
// ErrDeviceAuthorizationPending represents the server waiting on the user to complete the device flow
|
||||
ErrDeviceAuthorizationPending = fmt.Errorf("%s Error while retrieving OAuth token: Authorization Pending", logPrefix)
|
||||
|
||||
// ErrDeviceCodeExpired represents the server timing out and expiring the code during device flow
|
||||
ErrDeviceCodeExpired = fmt.Errorf("%s Error while retrieving OAuth token: Code Expired", logPrefix)
|
||||
|
||||
// ErrDeviceSlowDown represents the service telling us we're polling too often during device flow
|
||||
ErrDeviceSlowDown = fmt.Errorf("%s Error while retrieving OAuth token: Slow Down", logPrefix)
|
||||
|
||||
// ErrDeviceCodeEmpty represents an empty device code from the device endpoint while using device flow
|
||||
ErrDeviceCodeEmpty = fmt.Errorf("%s Error while retrieving device code: Device Code Empty", logPrefix)
|
||||
|
||||
// ErrOAuthTokenEmpty represents an empty OAuth token from the token endpoint when using device flow
|
||||
ErrOAuthTokenEmpty = fmt.Errorf("%s Error while retrieving OAuth token: Token Empty", logPrefix)
|
||||
|
||||
errCodeSendingFails = "Error occurred while sending request for Device Authorization Code"
|
||||
errCodeHandlingFails = "Error occurred while handling response from the Device Endpoint"
|
||||
errTokenSendingFails = "Error occurred while sending request with device code for a token"
|
||||
errTokenHandlingFails = "Error occurred while handling response from the Token Endpoint (during device flow)"
|
||||
errStatusNotOK = "Error HTTP status != 200"
|
||||
)
|
||||
|
||||
// DeviceCode is the object returned by the device auth endpoint
|
||||
// It contains information to instruct the user to complete the auth flow
|
||||
type DeviceCode struct {
|
||||
DeviceCode *string `json:"device_code,omitempty"`
|
||||
UserCode *string `json:"user_code,omitempty"`
|
||||
VerificationURL *string `json:"verification_url,omitempty"`
|
||||
ExpiresIn *int64 `json:"expires_in,string,omitempty"`
|
||||
Interval *int64 `json:"interval,string,omitempty"`
|
||||
|
||||
Message *string `json:"message"` // Azure specific
|
||||
Resource string // store the following, stored when initiating, used when exchanging
|
||||
OAuthConfig OAuthConfig
|
||||
ClientID string
|
||||
}
|
||||
|
||||
// TokenError is the object returned by the token exchange endpoint
|
||||
// when something is amiss
|
||||
type TokenError struct {
|
||||
Error *string `json:"error,omitempty"`
|
||||
ErrorCodes []int `json:"error_codes,omitempty"`
|
||||
ErrorDescription *string `json:"error_description,omitempty"`
|
||||
Timestamp *string `json:"timestamp,omitempty"`
|
||||
TraceID *string `json:"trace_id,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceToken is the object return by the token exchange endpoint
|
||||
// It can either look like a Token or an ErrorToken, so put both here
|
||||
// and check for presence of "Error" to know if we are in error state
|
||||
type deviceToken struct {
|
||||
Token
|
||||
TokenError
|
||||
}
|
||||
|
||||
// InitiateDeviceAuth initiates a device auth flow. It returns a DeviceCode
|
||||
// that can be used with CheckForUserCompletion or WaitForUserCompletion.
|
||||
// Deprecated: use InitiateDeviceAuthWithContext() instead.
|
||||
func InitiateDeviceAuth(sender Sender, oauthConfig OAuthConfig, clientID, resource string) (*DeviceCode, error) {
|
||||
return InitiateDeviceAuthWithContext(context.Background(), sender, oauthConfig, clientID, resource)
|
||||
}
|
||||
|
||||
// InitiateDeviceAuthWithContext initiates a device auth flow. It returns a DeviceCode
|
||||
// that can be used with CheckForUserCompletion or WaitForUserCompletion.
|
||||
func InitiateDeviceAuthWithContext(ctx context.Context, sender Sender, oauthConfig OAuthConfig, clientID, resource string) (*DeviceCode, error) {
|
||||
v := url.Values{
|
||||
"client_id": []string{clientID},
|
||||
"resource": []string{resource},
|
||||
}
|
||||
|
||||
s := v.Encode()
|
||||
body := ioutil.NopCloser(strings.NewReader(s))
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, oauthConfig.DeviceCodeEndpoint.String(), body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeSendingFails, err.Error())
|
||||
}
|
||||
|
||||
req.ContentLength = int64(len(s))
|
||||
req.Header.Set(contentType, mimeTypeFormPost)
|
||||
resp, err := sender.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeSendingFails, err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
rb, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, err.Error())
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, errStatusNotOK)
|
||||
}
|
||||
|
||||
if len(strings.Trim(string(rb), " ")) == 0 {
|
||||
return nil, ErrDeviceCodeEmpty
|
||||
}
|
||||
|
||||
var code DeviceCode
|
||||
err = json.Unmarshal(rb, &code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, err.Error())
|
||||
}
|
||||
|
||||
code.ClientID = clientID
|
||||
code.Resource = resource
|
||||
code.OAuthConfig = oauthConfig
|
||||
|
||||
return &code, nil
|
||||
}
|
||||
|
||||
// CheckForUserCompletion takes a DeviceCode and checks with the Azure AD OAuth endpoint
|
||||
// to see if the device flow has: been completed, timed out, or otherwise failed
|
||||
// Deprecated: use CheckForUserCompletionWithContext() instead.
|
||||
func CheckForUserCompletion(sender Sender, code *DeviceCode) (*Token, error) {
|
||||
return CheckForUserCompletionWithContext(context.Background(), sender, code)
|
||||
}
|
||||
|
||||
// CheckForUserCompletionWithContext takes a DeviceCode and checks with the Azure AD OAuth endpoint
|
||||
// to see if the device flow has: been completed, timed out, or otherwise failed
|
||||
func CheckForUserCompletionWithContext(ctx context.Context, sender Sender, code *DeviceCode) (*Token, error) {
|
||||
v := url.Values{
|
||||
"client_id": []string{code.ClientID},
|
||||
"code": []string{*code.DeviceCode},
|
||||
"grant_type": []string{OAuthGrantTypeDeviceCode},
|
||||
"resource": []string{code.Resource},
|
||||
}
|
||||
|
||||
s := v.Encode()
|
||||
body := ioutil.NopCloser(strings.NewReader(s))
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, code.OAuthConfig.TokenEndpoint.String(), body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenSendingFails, err.Error())
|
||||
}
|
||||
|
||||
req.ContentLength = int64(len(s))
|
||||
req.Header.Set(contentType, mimeTypeFormPost)
|
||||
resp, err := sender.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenSendingFails, err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
rb, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, err.Error())
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK && len(strings.Trim(string(rb), " ")) == 0 {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, errStatusNotOK)
|
||||
}
|
||||
if len(strings.Trim(string(rb), " ")) == 0 {
|
||||
return nil, ErrOAuthTokenEmpty
|
||||
}
|
||||
|
||||
var token deviceToken
|
||||
err = json.Unmarshal(rb, &token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, err.Error())
|
||||
}
|
||||
|
||||
if token.Error == nil {
|
||||
return &token.Token, nil
|
||||
}
|
||||
|
||||
switch *token.Error {
|
||||
case "authorization_pending":
|
||||
return nil, ErrDeviceAuthorizationPending
|
||||
case "slow_down":
|
||||
return nil, ErrDeviceSlowDown
|
||||
case "access_denied":
|
||||
return nil, ErrDeviceAccessDenied
|
||||
case "code_expired":
|
||||
return nil, ErrDeviceCodeExpired
|
||||
default:
|
||||
// return a more meaningful error message if available
|
||||
if token.ErrorDescription != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, *token.Error, *token.ErrorDescription)
|
||||
}
|
||||
return nil, ErrDeviceGeneric
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForUserCompletion calls CheckForUserCompletion repeatedly until a token is granted or an error state occurs.
|
||||
// This prevents the user from looping and checking against 'ErrDeviceAuthorizationPending'.
|
||||
// Deprecated: use WaitForUserCompletionWithContext() instead.
|
||||
func WaitForUserCompletion(sender Sender, code *DeviceCode) (*Token, error) {
|
||||
return WaitForUserCompletionWithContext(context.Background(), sender, code)
|
||||
}
|
||||
|
||||
// WaitForUserCompletionWithContext calls CheckForUserCompletion repeatedly until a token is granted or an error
|
||||
// state occurs. This prevents the user from looping and checking against 'ErrDeviceAuthorizationPending'.
|
||||
func WaitForUserCompletionWithContext(ctx context.Context, sender Sender, code *DeviceCode) (*Token, error) {
|
||||
intervalDuration := time.Duration(*code.Interval) * time.Second
|
||||
waitDuration := intervalDuration
|
||||
|
||||
for {
|
||||
token, err := CheckForUserCompletionWithContext(ctx, sender, code)
|
||||
|
||||
if err == nil {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
switch err {
|
||||
case ErrDeviceSlowDown:
|
||||
waitDuration += waitDuration
|
||||
case ErrDeviceAuthorizationPending:
|
||||
// noop
|
||||
default: // everything else is "fatal" to us
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if waitDuration > (intervalDuration * 3) {
|
||||
return nil, fmt.Errorf("%s Error waiting for user to complete device flow. Server told us to slow_down too much", logPrefix)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(waitDuration):
|
||||
// noop
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user