Compare commits
401 Commits
v0.11.1
...
pires/add_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63f85cc062 | ||
|
|
7bcacb1cab | ||
|
|
a610358f56 | ||
|
|
704b01eac6 | ||
|
|
94999fc0b6 | ||
|
|
9d8005e4b8 | ||
|
|
a486eaffd2 | ||
|
|
d66366ba96 | ||
|
|
bb4e20435d | ||
|
|
6feafcf018 | ||
|
|
5db1443e33 | ||
|
|
2c4442b17f | ||
|
|
70848cfdae | ||
|
|
c668ae6ab6 | ||
|
|
f7f8b45117 | ||
|
|
5001135763 | ||
|
|
67be3c681d | ||
|
|
fca742986c | ||
|
|
db7f53c1ca | ||
|
|
c63b8f0dec | ||
|
|
83fbc0c687 | ||
|
|
d2523fe808 | ||
|
|
7ee822ec6d | ||
|
|
0b70fb1958 | ||
|
|
a25c1def45 | ||
|
|
d682bb3894 | ||
|
|
6198b02423 | ||
|
|
9d94eea9e9 | ||
|
|
de4fe42586 | ||
|
|
305e33bfbf | ||
|
|
00d8340a64 | ||
|
|
5e5a842dbb | ||
|
|
aa94284712 | ||
|
|
d87dd1c79f | ||
|
|
ab3615b8d7 | ||
|
|
22d2416dc4 | ||
|
|
e1c6e80a7a | ||
|
|
1ed3180ec2 | ||
|
|
97452b493f | ||
|
|
6363360781 | ||
|
|
44d0df547d | ||
|
|
10a7559b83 | ||
|
|
b98ba29b52 | ||
|
|
008fe17b91 | ||
|
|
ec1fe2070a | ||
|
|
48e29d75fc | ||
|
|
f617ccebc5 | ||
|
|
433e0bbd20 | ||
|
|
a8f253088c | ||
|
|
38e662129d | ||
|
|
95bdbdec0d | ||
|
|
1958686b4a | ||
|
|
36397f80c2 | ||
|
|
801b44543c | ||
|
|
853f9ead1c | ||
|
|
915445205f | ||
|
|
2b7e4c9dc6 | ||
|
|
410e05878a | ||
|
|
70c7745444 | ||
|
|
269ef14a7a | ||
|
|
faaf14c68d | ||
|
|
7c9bd20eea | ||
|
|
c9c0d99064 | ||
|
|
4974e062d0 | ||
|
|
e1342777d6 | ||
|
|
597e7dc281 | ||
|
|
a9a0ee50cf | ||
|
|
5fe8a7d000 | ||
|
|
22f329fcf0 | ||
|
|
09ad3fe644 | ||
|
|
68347d4ed1 | ||
|
|
92f8661031 | ||
|
|
f63c23108f | ||
|
|
db5bf2b0d3 | ||
|
|
fbf6a1957f | ||
|
|
e6fc00e8dd | ||
|
|
50f1346977 | ||
|
|
66fc9d476f | ||
|
|
0543245668 | ||
|
|
d245d9b8cf | ||
|
|
4fe8496dd1 | ||
|
|
5cd25230c5 | ||
|
|
04cdec767b | ||
|
|
822dc8bb4a | ||
|
|
40b4425804 | ||
|
|
be0a062aec | ||
|
|
a2515d859a | ||
|
|
0df7ac4e80 | ||
|
|
96eae1906b | ||
|
|
8437e237be | ||
|
|
baa0e6e8fc | ||
|
|
405d5d63b1 | ||
|
|
e1486ade00 | ||
|
|
4c223a8cd9 | ||
|
|
bf3a764409 | ||
|
|
b259cb0548 | ||
|
|
e95023b76e | ||
|
|
5fd08d4619 | ||
|
|
c40a255eae | ||
|
|
616538ef01 | ||
|
|
c4582ccfbc | ||
|
|
7feb175720 | ||
|
|
0e1cc1566e | ||
|
|
d11968a0fd | ||
|
|
3ff1694252 | ||
|
|
53e96e03a9 | ||
|
|
3a361ebabd | ||
|
|
ac9a1af564 | ||
|
|
fd3da8dcad | ||
|
|
731d0d6f5c | ||
|
|
2ac4ff9b35 | ||
|
|
82452a73a5 | ||
|
|
2fa03a15a2 | ||
|
|
eb7553e6c4 | ||
|
|
3cfd4737dc | ||
|
|
346c20c005 | ||
|
|
fa139bfe27 | ||
|
|
cb0e18e6a1 | ||
|
|
379031eb61 | ||
|
|
25b8c546a0 | ||
|
|
53fcbe7abe | ||
|
|
7662a48922 | ||
|
|
46bfb01cd4 | ||
|
|
c2b4863f40 | ||
|
|
5edfe23bd5 | ||
|
|
c9969ee33d | ||
|
|
ff61469113 | ||
|
|
20c064848a | ||
|
|
9e711f3276 | ||
|
|
be76c022ae | ||
|
|
e82e46e5de | ||
|
|
eda7adbdb4 | ||
|
|
9affe97f88 | ||
|
|
9e522952c3 | ||
|
|
99ad66814b | ||
|
|
9745a6a9bc | ||
|
|
3830b0ed79 | ||
|
|
1b8597647b | ||
|
|
735eb34829 | ||
|
|
8affa1c42a | ||
|
|
076b28d2b5 | ||
|
|
de7f7dd173 | ||
|
|
0d1f6f1625 | ||
|
|
d29adf5ce3 | ||
|
|
ffbfe19e78 | ||
|
|
9a60ea2494 | ||
|
|
c0d5809285 | ||
|
|
bbe4551940 | ||
|
|
ca84620958 | ||
|
|
4fd2b754b5 | ||
|
|
52e823a2c1 | ||
|
|
11c63bca6f | ||
|
|
d64d427ec8 | ||
|
|
d562b71d9a | ||
|
|
966a960eef | ||
|
|
9454f1fde8 | ||
|
|
8812427117 | ||
|
|
331a13f514 | ||
|
|
42f7c56d32 | ||
|
|
7af4ea5b0a | ||
|
|
5bef4ea63b | ||
|
|
84a2528741 | ||
|
|
1a2c0bb029 | ||
|
|
93d9a5c9f8 | ||
|
|
4d271c5b53 | ||
|
|
258e809721 | ||
|
|
0b946848ee | ||
|
|
79d2ef1f12 | ||
|
|
21ffe6f0ae | ||
|
|
af77bc8364 | ||
|
|
9883707707 | ||
|
|
afe0f52689 | ||
|
|
06c089843e | ||
|
|
affbd27827 | ||
|
|
5f1a53c580 | ||
|
|
05e9aa2858 | ||
|
|
1c581260d5 | ||
|
|
cd68e70627 | ||
|
|
5f63e9d30a | ||
|
|
544cca975f | ||
|
|
b4e62a645b | ||
|
|
ba79256b1d | ||
|
|
2716c38e1f | ||
|
|
6b8e772dad | ||
|
|
c437e05ad0 | ||
|
|
b9303714de | ||
|
|
621ee4bb1c | ||
|
|
b793d89c66 | ||
|
|
590d2e7f01 | ||
|
|
d53d86053c | ||
|
|
a34a4f0c9f | ||
|
|
b91d892bff | ||
|
|
84a169f25d | ||
|
|
946c616c67 | ||
|
|
3c5e8fb6b1 | ||
|
|
1c32b2c8ee | ||
|
|
cf2d5264a5 | ||
|
|
0c64171e85 | ||
|
|
4b74a01f8f | ||
|
|
9b4dc639cf | ||
|
|
724bddd559 | ||
|
|
5f5f23e668 | ||
|
|
5304a66f90 | ||
|
|
c1bc314c38 | ||
|
|
3d1226d45d | ||
|
|
f248259fb9 | ||
|
|
cd059d9755 | ||
|
|
66ee454e1a | ||
|
|
6845cf825a | ||
|
|
bad6076431 | ||
|
|
d390dfce43 | ||
|
|
49c596c5ca | ||
|
|
3fc79dc677 | ||
|
|
4bdcba5b85 | ||
|
|
40289aeb07 | ||
|
|
851f5abedc | ||
|
|
c0296b99fd | ||
|
|
83f8cd1a58 | ||
|
|
657e6aca77 | ||
|
|
5a39c167a6 | ||
|
|
c19bac7ed8 | ||
|
|
af1df79088 | ||
|
|
0b7e66d57c | ||
|
|
56b248c854 | ||
|
|
4258c46746 | ||
|
|
1e9e055e89 | ||
|
|
12625131b5 | ||
|
|
ee7f5fa3ef | ||
|
|
bcb5dfa11c | ||
|
|
b7c19cb5a1 | ||
|
|
2f989f5278 | ||
|
|
5455f290a4 | ||
|
|
9c6b48c1c3 | ||
|
|
e00e4c2bba | ||
|
|
72a0be3f45 | ||
|
|
845b4cd409 | ||
|
|
f934ded4a2 | ||
|
|
364d7a9a74 | ||
|
|
e805cb744a | ||
|
|
56a39032e9 | ||
|
|
b50302b845 | ||
|
|
da1cb98b5d | ||
|
|
5306173408 | ||
|
|
d6a38ca721 | ||
|
|
30aabe6fcb | ||
|
|
1e8c16877d | ||
|
|
bd977cb224 | ||
|
|
ca417d5239 | ||
|
|
fedffd6f2c | ||
|
|
e72e31b0d8 | ||
|
|
a667c5113b | ||
|
|
bfd3f51ff3 | ||
|
|
5f64eab109 | ||
|
|
2398504d08 | ||
|
|
05fc1a43be | ||
|
|
3db9ab97c6 | ||
|
|
dfca10f0dc | ||
|
|
ee93284f38 | ||
|
|
bef6eda40d | ||
|
|
b3213d6eb2 | ||
|
|
a67cfab42b | ||
|
|
51b9a6c40d | ||
|
|
8fc8b69d8f | ||
|
|
3e0d03c833 | ||
|
|
7628c13aeb | ||
|
|
8308033eff | ||
|
|
d9193e2440 | ||
|
|
30e31c0451 | ||
|
|
70f1a93c6e | ||
|
|
063f1fcdbc | ||
|
|
de8226751d | ||
|
|
47a353897e | ||
|
|
3ec3b14e49 | ||
|
|
5ad12cd476 | ||
|
|
230ebe1b29 | ||
|
|
554d30a0b1 | ||
|
|
5c1c3886e0 | ||
|
|
4fea631791 | ||
|
|
5995a2a18d | ||
|
|
fb33c2e144 | ||
|
|
47112aa5d6 | ||
|
|
0bdf742303 | ||
|
|
4162bba465 | ||
|
|
55f3f17ba0 | ||
|
|
7f2a022915 | ||
|
|
6e33b0f084 | ||
|
|
1a9c4bfb24 | ||
|
|
c258614d8f | ||
|
|
3783a39b26 | ||
|
|
ba940a9739 | ||
|
|
0ccf5059e4 | ||
|
|
31c8fbaa41 | ||
|
|
4ee2c4d370 | ||
|
|
c314045d60 | ||
|
|
d455bd16fc | ||
|
|
d22265e5f5 | ||
|
|
871424368f | ||
|
|
cdc261a08d | ||
|
|
d878af3262 | ||
|
|
4202b03cda | ||
|
|
b3aa0f577b | ||
|
|
f80f823e8b | ||
|
|
1bd53c15d1 | ||
|
|
6f6b92ba57 | ||
|
|
bcfc2accf8 | ||
|
|
b712751c6d | ||
|
|
11321d5092 | ||
|
|
e02c4d9e1e | ||
|
|
eda3e27c9f | ||
|
|
e37a5cebca | ||
|
|
c0746372ad | ||
|
|
82a430ccf7 | ||
|
|
8a5f4af171 | ||
|
|
9510b370cf | ||
|
|
ea8495c3a1 | ||
|
|
334baa73cf | ||
|
|
bb9ff1adf3 | ||
|
|
db146a0e01 | ||
|
|
fdb0c805f7 | ||
|
|
dc7ff44303 | ||
|
|
e7a36c3505 | ||
|
|
f10a16aed7 | ||
|
|
da57373abb | ||
|
|
33df981904 | ||
|
|
ecf6e45bfc | ||
|
|
3f85705461 | ||
|
|
7133a372d6 | ||
|
|
5949e6279d | ||
|
|
9cce8640a5 | ||
|
|
7accddcaf4 | ||
|
|
ee31118596 | ||
|
|
2507f57f97 | ||
|
|
9a461a61ad | ||
|
|
9443e32ae7 | ||
|
|
43ee086360 | ||
|
|
0c6de30684 | ||
|
|
7305c08d7e | ||
|
|
ccb6713b86 | ||
|
|
2f2625c8e2 | ||
|
|
69f1186713 | ||
|
|
89d88a17ed | ||
|
|
cad19238fd | ||
|
|
bc2f6e0dc4 | ||
|
|
47f5aa45df | ||
|
|
de238ee280 | ||
|
|
569706f371 | ||
|
|
cb307df71e | ||
|
|
40a4b54ca7 | ||
|
|
edc0991c0c | ||
|
|
fbed4ca702 | ||
|
|
9b27eb83fe | ||
|
|
3b3bf3ff20 | ||
|
|
75a399f6f4 | ||
|
|
f0a0e8cbfe | ||
|
|
32ff40eb56 | ||
|
|
65c5446c94 | ||
|
|
cafcdeeefa | ||
|
|
5c2b682cdc | ||
|
|
e1c3bc3151 | ||
|
|
5ac33e4b0a | ||
|
|
42656aae2f | ||
|
|
10b291dba1 | ||
|
|
9d90c599e7 | ||
|
|
82de7f02c4 | ||
|
|
ad6cd7d552 | ||
|
|
a28969355e | ||
|
|
75a1877d9f | ||
|
|
a87af0818f | ||
|
|
3efc9229ba | ||
|
|
d0c91a1933 | ||
|
|
7188238caa | ||
|
|
9a7698b09f | ||
|
|
50bbc3d1d4 | ||
|
|
bd8e39e3f9 | ||
|
|
ce38d72c0e | ||
|
|
4a270fea08 | ||
|
|
2974de3961 | ||
|
|
4d60fc2049 | ||
|
|
28dac027ce | ||
|
|
732c0a82d6 | ||
|
|
b056ac08bb | ||
|
|
ce60fb81d4 | ||
|
|
46591ad811 | ||
|
|
0ba0200067 | ||
|
|
29d2bd251d | ||
|
|
d3ff785e08 | ||
|
|
8493cbb42a | ||
|
|
e7e692bcb6 | ||
|
|
7bd0bd0f0e | ||
|
|
10126924f9 | ||
|
|
afaae4454f | ||
|
|
f7fee27790 | ||
|
|
327c6cf319 | ||
|
|
1c4aa5d575 | ||
|
|
ab3086436e | ||
|
|
ad943ebbd8 | ||
|
|
3371c727a6 | ||
|
|
1bab3ea80f | ||
|
|
3a88dfd413 | ||
|
|
5854904f0d | ||
|
|
905709c646 |
@@ -1,132 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
validate:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
environment:
|
||||
GO111MODULE: "on"
|
||||
GOPROXY: https://proxy.golang.org
|
||||
working_directory: /go/src/github.com/virtual-kubelet/virtual-kubelet
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: go vet
|
||||
command: V=1 CI=1 make vet
|
||||
- run:
|
||||
name: Install linters
|
||||
command: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.15.0 && mv ./bin/* /go/bin/
|
||||
- run:
|
||||
name: Lint
|
||||
command: golangci-lint run --new-from-rev "HEAD~$(git rev-list master.. --count)" ./...
|
||||
- run:
|
||||
name: Dependencies
|
||||
command: scripts/validate/gomod.sh
|
||||
|
||||
test:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
environment:
|
||||
GO111MODULE: "on"
|
||||
working_directory: /go/src/github.com/virtual-kubelet/virtual-kubelet
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Build
|
||||
command: V=1 make build
|
||||
- run:
|
||||
name: Install Nomad
|
||||
command: |
|
||||
curl \
|
||||
--silent \
|
||||
--location \
|
||||
--output nomad.zip \
|
||||
https://releases.hashicorp.com/nomad/0.8.6/nomad_0.8.6_linux_amd64.zip && \
|
||||
unzip nomad.zip && \
|
||||
chmod +x nomad && \
|
||||
mv nomad /go/bin/nomad && \
|
||||
rm nomad.zip
|
||||
- run:
|
||||
name: Tests
|
||||
command: V=1 CI=1 SKIP_AWS_E2E=1 make test
|
||||
|
||||
e2e:
|
||||
machine:
|
||||
image: circleci/classic:201808-01
|
||||
working_directory: /home/circleci/go/src/github.com/virtual-kubelet/virtual-kubelet
|
||||
environment:
|
||||
CHANGE_MINIKUBE_NONE_USER: true
|
||||
GOPATH: /home/circleci/go
|
||||
KUBECONFIG: /home/circleci/.kube/config
|
||||
KUBERNETES_VERSION: v1.12.3
|
||||
MINIKUBE_HOME: /home/circleci
|
||||
MINIKUBE_VERSION: v0.30.0
|
||||
MINIKUBE_WANTUPDATENOTIFICATION: false
|
||||
MINIKUBE_WANTREPORTERRORPROMPT: false
|
||||
SKAFFOLD_VERSION: v0.18.0
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install kubectl
|
||||
command: |
|
||||
curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${KUBERNETES_VERSION}/bin/linux/amd64/kubectl
|
||||
chmod +x kubectl
|
||||
sudo mv kubectl /usr/local/bin/
|
||||
mkdir -p ${HOME}/.kube
|
||||
touch ${HOME}/.kube/config
|
||||
- run:
|
||||
name: Install Skaffold
|
||||
command: |
|
||||
curl -Lo skaffold https://github.com/GoogleContainerTools/skaffold/releases/download/${SKAFFOLD_VERSION}/skaffold-linux-amd64
|
||||
chmod +x skaffold
|
||||
sudo mv skaffold /usr/local/bin/
|
||||
- run:
|
||||
name: Install Minikube
|
||||
command: |
|
||||
curl -Lo minikube https://github.com/kubernetes/minikube/releases/download/${MINIKUBE_VERSION}/minikube-linux-amd64
|
||||
chmod +x minikube
|
||||
sudo mv minikube /usr/local/bin/
|
||||
- run:
|
||||
name: Start Minikube
|
||||
command: |
|
||||
sudo -E minikube start --vm-driver=none --cpus 2 --memory 2048 --kubernetes-version=${KUBERNETES_VERSION}
|
||||
- run:
|
||||
name: Wait for Minikube
|
||||
command: |
|
||||
JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}';
|
||||
until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do
|
||||
sleep 1;
|
||||
done
|
||||
- run:
|
||||
name: Watch pods
|
||||
command: kubectl get pods -o json --watch
|
||||
background: true
|
||||
- run:
|
||||
name: Watch nodes
|
||||
command: kubectl get nodes -o json --watch
|
||||
background: true
|
||||
- run:
|
||||
name: Run the end-to-end test suite
|
||||
command: |
|
||||
mkdir $HOME/.go
|
||||
export PATH=$HOME/.go/bin:${PATH}
|
||||
curl -fsSL -o "/tmp/go.tar.gz" "https://dl.google.com/go/go1.12.5.linux-amd64.tar.gz"
|
||||
tar -C $HOME/.go --strip-components=1 -xzf "/tmp/go.tar.gz"
|
||||
go version
|
||||
make e2e
|
||||
- run:
|
||||
name: Collect logs on failure from vkubelet-mock-0
|
||||
command: |
|
||||
kubectl logs vkubelet-mock-0
|
||||
when: on_fail
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
validate_and_test:
|
||||
jobs:
|
||||
- validate
|
||||
- test
|
||||
- e2e:
|
||||
requires:
|
||||
- validate
|
||||
- test
|
||||
@@ -1,4 +1,6 @@
|
||||
.vscode
|
||||
private.env
|
||||
*.private.*
|
||||
providers/azurebatch/deployment/
|
||||
providers/azurebatch/deployment/
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
|
||||
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
110
.github/workflows/ci.yml
vendored
Normal file
110
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
name: CI
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v3
|
||||
- uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.48.0
|
||||
args: --timeout=5m
|
||||
|
||||
unit-tests:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run Tests
|
||||
run: make test
|
||||
|
||||
env-tests:
|
||||
name: Envtest Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run Tests
|
||||
run: make envtest
|
||||
|
||||
e2e:
|
||||
name: E2E
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CHANGE_MINIKUBE_NONE_USER: true
|
||||
KUBERNETES_VERSION: v1.20.1
|
||||
MINIKUBE_HOME: /home/circleci
|
||||
MINIKUBE_VERSION: v1.16.0
|
||||
MINIKUBE_WANTUPDATENOTIFICATION: false
|
||||
MINIKUBE_WANTREPORTERRORPROMPT: false
|
||||
SKAFFOLD_VERSION: v1.17.2
|
||||
GO111MODULE: "on"
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Skaffold
|
||||
run: |
|
||||
curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/${SKAFFOLD_VERSION}/skaffold-linux-amd64
|
||||
chmod +x skaffold
|
||||
sudo mv skaffold /usr/local/bin/
|
||||
echo /usr/local/bin >> $GITHUB_PATH
|
||||
- name: Install Minikube dependencies
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
|
||||
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
|
||||
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
|
||||
deb https://apt.kubernetes.io/ kubernetes-xenial main
|
||||
EOF
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y kubelet # systemd unit is disabled
|
||||
- name: Install Minikube
|
||||
run: |
|
||||
curl -Lo minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64
|
||||
chmod +x minikube
|
||||
sudo mv minikube /usr/local/bin/
|
||||
- name: Start Minikube
|
||||
run: |
|
||||
sudo -E PATH=$PATH minikube start --vm-driver=none --cpus 2 --memory 2048 --kubernetes-version=${KUBERNETES_VERSION}
|
||||
- name: Wait for Minikube
|
||||
run: |
|
||||
JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}';
|
||||
until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do
|
||||
sleep 1;
|
||||
done
|
||||
- name: Run Tests
|
||||
run: make e2e
|
||||
59
.github/workflows/codeql-analysis.yml
vendored
Normal file
59
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: "CodeQL"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: "19 18 * * 3"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ["go"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
@@ -2,12 +2,38 @@ linter-settings:
|
||||
lll:
|
||||
line-length: 200
|
||||
|
||||
timeout: 10m
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
# This directory contains copy code from upstream kubernetes/kubernetes, skip it.
|
||||
- internal/kubernetes
|
||||
# This is mostly copied from upstream, rather than fixing that code here just ignore the errors.
|
||||
- internal/podutils
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- errcheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- golint
|
||||
- goconst
|
||||
- structcheck
|
||||
- varcheck
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- gofmt
|
||||
- goimports
|
||||
- ineffassign
|
||||
- vet
|
||||
- unused
|
||||
- misspell
|
||||
- gosec
|
||||
- exportloopref # Checks for pointers to enclosing loop variables
|
||||
- tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17
|
||||
|
||||
linters-settings:
|
||||
gosec:
|
||||
excludes:
|
||||
- G304
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude:
|
||||
# EXC0001 errcheck: Almost all programs ignore errors on these functions and in most cases it's ok
|
||||
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). (is not checked|Errors unhandled)
|
||||
|
||||
14
ADOPTERS.md
14
ADOPTERS.md
@@ -1,3 +1,15 @@
|
||||
## Virtual Kubelet adopters
|
||||
|
||||
Are you currently using Virtual Kubelet in production? Please let us know by adding your company name and a description of your use case to this document!
|
||||
* Microsoft Azure
|
||||
* AWS
|
||||
* Alibaba
|
||||
* VMWare
|
||||
* Netflix
|
||||
* Hashi Corp
|
||||
* Admiralty
|
||||
* Elotl
|
||||
* Tencent Games
|
||||
|
||||
Since end-users are specific per provider within VK we have many end-user customers that we don't have permission to list publically. Please contact ribhatia@microsoft.com for more informtation.
|
||||
|
||||
Are you currently using Virtual Kubelet in production? Please let us know by adding your company name and a description of your use case to this document!
|
||||
|
||||
@@ -77,6 +77,70 @@ git remote add upstream git@github.com:virtual-kubelet/virtual-kubelet.git
|
||||
```
|
||||
3. Submit a pull request.
|
||||
|
||||
|
||||
## Submission and Review guidelines
|
||||
|
||||
We welcome and appreciate everyone to submit and review changes. Here are some guidelines to follow for help ensure
|
||||
a successful contribution experience.
|
||||
|
||||
Please note these are general guidelines, and while they are a good starting point, they are not specifically rules.
|
||||
If you have a question about something, feel free to ask:
|
||||
|
||||
- [#virtual-kubelet](https://kubernetes.slack.com/archives/C8YU1QP8W) on Kubernetes Slack
|
||||
- [virtualkubelet-dev@lists.cncf.io](mailto:virtualkubelet-dev@lists.cncf.io)
|
||||
- GitHub Issues
|
||||
|
||||
#### Don't make breaking API changes.
|
||||
|
||||
Since Virtual Kubelet has reached 1.0 it is a major goal of the project to keep a stable API.
|
||||
Breaking changes must only be considered if a 2.0 release is on the table, which should only come with thoughtful
|
||||
consideration of the projects users as well as maintenance burden.
|
||||
|
||||
Also note that behavior changes in the runtime can have cascading effects that cause unintended failures. Behavior
|
||||
changes should come well documented and with ample consideration for downstream effects. If possible, they should be
|
||||
opt-in.
|
||||
|
||||
#### Public APIs
|
||||
|
||||
Public API's should be extendable and flexible without requiring breaking changes.
|
||||
While we can always add a new function (`Foo2()`), a new type, etc, doing so makes it harder for people to update to
|
||||
the new behavior.
|
||||
|
||||
Build API interfaces that do not need to be changed to adopt new or improved functionality. Opinions on how a particular
|
||||
thing should work should be encoded by the user rather than implicit in the runtime. Defaults are fine, but defaults
|
||||
should be overridable.
|
||||
|
||||
The smaller the surface area of an API, the easier it is to do more interesting things with it.
|
||||
|
||||
#### Building blocks
|
||||
|
||||
Don't overload functionality. If something is complicated to setup we can provide helpers or wrappers to do that, but
|
||||
don't require users to do things a certain way because this tends to diminish the usefulness, especially as it relates
|
||||
to runtimes.
|
||||
|
||||
We also do not want the maintenance burden of every users individual edge cases.
|
||||
|
||||
#### Use context.Context
|
||||
|
||||
Probably if it is a public/exported API, it should take a `context.Context`. Even if it doesn't need one today, it may
|
||||
need it tomorrow, and then we have a breaking API change.
|
||||
|
||||
We use `context.Context` for storing loggers, tracing spans, and cancellation all across the project. Better safe
|
||||
than sorry: add a `context.Context`.
|
||||
|
||||
#### Errors
|
||||
|
||||
Callers can't handle errors if they don't know what the error is, so make sure they can figure that out.
|
||||
We use a package `errdefs` to define the types of errors we currently look out for. We do not typically look for
|
||||
concrete error types, so check out `errdefs` and see if there is already an error type in there for your needs, or even
|
||||
create a new one.
|
||||
|
||||
#### Testing
|
||||
|
||||
Ideally all behavior would be tested, in practice this is not the case. Unit tests are great, and fast. There is also
|
||||
an end-to-end test suite for testing the overall behavior of the system. Please add tests. This is also a great place
|
||||
to get started if you are new to the codebase.
|
||||
|
||||
## Code of conduct
|
||||
|
||||
Virtual Kubelet follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
20
Dockerfile
20
Dockerfile
@@ -1,4 +1,6 @@
|
||||
FROM golang:1.12 as builder
|
||||
ARG GOLANG_CI_LINT_VERSION
|
||||
|
||||
FROM golang:1.18 as builder
|
||||
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
||||
ENV GOPATH /go
|
||||
COPY . /go/src/github.com/virtual-kubelet/virtual-kubelet
|
||||
@@ -7,6 +9,22 @@ ARG BUILD_TAGS=""
|
||||
RUN make VK_BUILD_TAGS="${BUILD_TAGS}" build
|
||||
RUN cp bin/virtual-kubelet /usr/bin/virtual-kubelet
|
||||
|
||||
FROM golangci/golangci-lint:${GOLANG_CI_LINT_VERSION} as lint
|
||||
WORKDIR /app
|
||||
COPY go.mod ./
|
||||
COPY go.sum ./
|
||||
RUN \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go mod download
|
||||
COPY . .
|
||||
ARG OUT_FORMAT
|
||||
RUN \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/golangci-lint \
|
||||
golangci-lint run -v --out-format="${OUT_FORMAT:-colored-line-number}"
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /usr/bin/virtual-kubelet /usr/bin/virtual-kubelet
|
||||
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs
|
||||
|
||||
101
Makefile
101
Makefile
@@ -5,6 +5,8 @@ exec := $(DOCKER_IMAGE)
|
||||
github_repo := virtual-kubelet/virtual-kubelet
|
||||
binary := virtual-kubelet
|
||||
|
||||
GOTEST ?= go test $(if $V,-v)
|
||||
|
||||
export GO111MODULE ?= on
|
||||
|
||||
include Makefile.e2e
|
||||
@@ -13,17 +15,20 @@ include Makefile.e2e
|
||||
# Also, we will want to lock our tool versions using go mod:
|
||||
# https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module
|
||||
gobin_tool ?= $(shell which gobin || echo $(GOPATH)/bin/gobin)
|
||||
goimports := golang.org/x/tools/cmd/goimports@release-branch.go1.12
|
||||
goimports := golang.org/x/tools/cmd/goimports@release-branch.go1.15
|
||||
gocovmerge := github.com/wadey/gocovmerge@b5bfa59ec0adc420475f97f89b58045c721d761c
|
||||
goreleaser := github.com/goreleaser/goreleaser@v0.82.2
|
||||
gox := github.com/mitchellh/gox@v1.0.1
|
||||
|
||||
# comment this line out for quieter things
|
||||
# V := 1 # When V is set, print commands and build progress.
|
||||
# V := 1 # When V is set, try to enable extra logging for debugging
|
||||
|
||||
# Space separated patterns of packages to skip in list, test, format.
|
||||
IGNORED_PACKAGES := /vendor/
|
||||
|
||||
TEST_OS := $(shell go env GOOS)
|
||||
TEST_ARCH := $(shell go env GOARCH)
|
||||
|
||||
.PHONY: all
|
||||
all: test build
|
||||
|
||||
@@ -31,19 +36,19 @@ all: test build
|
||||
# safebuild builds inside a docker container with no clingons from your $GOPATH
|
||||
safebuild:
|
||||
@echo "Building..."
|
||||
$Q docker build --build-arg BUILD_TAGS="$(VK_BUILD_TAGS)" -t $(DOCKER_IMAGE):$(VERSION) .
|
||||
docker build --build-arg BUILD_TAGS="$(VK_BUILD_TAGS)" -t $(DOCKER_IMAGE):$(VERSION) .
|
||||
|
||||
.PHONY: build
|
||||
build: build_tags := netgo osusergo
|
||||
build: OUTPUT_DIR ?= bin
|
||||
build: authors
|
||||
@echo "Building..."
|
||||
$Q CGO_ENABLED=0 go build -ldflags '-extldflags "-static"' -o $(OUTPUT_DIR)/$(binary) $(if $V,-v) $(VERSION_FLAGS) ./cmd/$(binary)
|
||||
CGO_ENABLED=0 go build -ldflags '-extldflags "-static"' -o $(OUTPUT_DIR)/$(binary) $(if $V,-v) $(VERSION_FLAGS) ./cmd/$(binary)
|
||||
|
||||
.PHONY: tags
|
||||
tags:
|
||||
@echo "Listing tags..."
|
||||
$Q @git tag
|
||||
@git tag
|
||||
|
||||
.PHONY: release
|
||||
release: build goreleaser
|
||||
@@ -54,73 +59,63 @@ release: build goreleaser
|
||||
.PHONY: clean test list cover format docker
|
||||
mod:
|
||||
@echo "Prune Dependencies..."
|
||||
$Q go mod tidy
|
||||
go mod tidy
|
||||
|
||||
docker:
|
||||
@echo "Docker Build..."
|
||||
$Q docker build --build-arg BUILD_TAGS="$(VK_BUILD_TAGS)" -t $(DOCKER_IMAGE) .
|
||||
docker build --build-arg BUILD_TAGS="$(VK_BUILD_TAGS)" -t $(DOCKER_IMAGE) .
|
||||
|
||||
clean:
|
||||
@echo "Clean..."
|
||||
$Q rm -rf bin
|
||||
rm -rf bin
|
||||
|
||||
vet:
|
||||
@echo "go vet'ing..."
|
||||
ifndef CI
|
||||
@echo "go vet'ing Outside CI..."
|
||||
$Q go vet $(allpackages)
|
||||
go vet $(TESTDIRS)
|
||||
else
|
||||
@echo "go vet'ing in CI..."
|
||||
$Q mkdir -p test
|
||||
$Q ( go vet $(allpackages); echo $$? ) | \
|
||||
mkdir -p test
|
||||
( go vet $(TESTDIRS); echo $$? ) | \
|
||||
tee test/vet.txt | sed '$$ d'; exit $$(tail -1 test/vet.txt)
|
||||
endif
|
||||
|
||||
test:
|
||||
ifndef CI
|
||||
@echo "Testing..."
|
||||
$Q go test $(if $V,-v) $(allpackages)
|
||||
else
|
||||
@echo "Testing in CI..."
|
||||
$Q mkdir -p test
|
||||
$Q ( GODEBUG=cgocheck=2 go test -v $(allpackages); echo $$? ) | \
|
||||
tee test/output.txt | sed '$$ d'; exit $$(tail -1 test/output.txt)
|
||||
endif
|
||||
$(GOTEST) $(TESTDIRS)
|
||||
|
||||
list:
|
||||
@echo "List..."
|
||||
@echo $(allpackages)
|
||||
@echo $(TESTDIRS)
|
||||
|
||||
cover: gocovmerge
|
||||
@echo "Coverage Report..."
|
||||
@echo "NOTE: make cover does not exit 1 on failure, don't use it to check for tests success!"
|
||||
$Q rm -f .GOPATH/cover/*.out cover/all.merged
|
||||
rm -f .GOPATH/cover/*.out cover/all.merged
|
||||
$(if $V,@echo "-- go test -coverpkg=./... -coverprofile=cover/... ./...")
|
||||
@for MOD in $(allpackages); do \
|
||||
go test -coverpkg=`echo $(allpackages)|tr " " ","` \
|
||||
@for MOD in $(TESTDIRS); do \
|
||||
go test -coverpkg=`echo $(TESTDIRS)|tr " " ","` \
|
||||
-coverprofile=cover/unit-`echo $$MOD|tr "/" "_"`.out \
|
||||
$$MOD 2>&1 | grep -v "no packages being tested depend on"; \
|
||||
done
|
||||
$Q $(gobin_tool) -run $(gocovmerge) cover/*.out > cover/all.merged
|
||||
$(gobin_tool) -run $(gocovmerge) cover/*.out > cover/all.merged
|
||||
ifndef CI
|
||||
@echo "Coverage Report..."
|
||||
$Q go tool cover -html .GOPATH/cover/all.merged
|
||||
go tool cover -html .GOPATH/cover/all.merged
|
||||
else
|
||||
@echo "Coverage Report In CI..."
|
||||
$Q go tool cover -html .GOPATH/cover/all.merged -o .GOPATH/cover/all.html
|
||||
go tool cover -html .GOPATH/cover/all.merged -o .GOPATH/cover/all.html
|
||||
endif
|
||||
@echo ""
|
||||
@echo "=====> Total test coverage: <====="
|
||||
@echo ""
|
||||
$Q go tool cover -func .GOPATH/cover/all.merged
|
||||
go tool cover -func .GOPATH/cover/all.merged
|
||||
|
||||
format: goimports
|
||||
@echo "Formatting..."
|
||||
$Q find . -iname \*.go | grep -v \
|
||||
find . -iname \*.go | grep -v \
|
||||
-e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)) | xargs $(gobin_tool) -run $(goimports) -w
|
||||
|
||||
|
||||
|
||||
##### =====> Internals <===== #####
|
||||
|
||||
.PHONY: setup
|
||||
@@ -141,11 +136,7 @@ VERSION := $(shell git describe --tags --always --dirty="-dev")
|
||||
DATE := $(shell date -u '+%Y-%m-%d-%H:%M UTC')
|
||||
VERSION_FLAGS := -ldflags='-X "main.buildVersion=$(VERSION)" -X "main.buildTime=$(DATE)"'
|
||||
|
||||
# assuming go 1.9 here!!
|
||||
_allpackages = $(shell go list ./...)
|
||||
|
||||
# memoize allpackages, so that it's executed only once and only if used
|
||||
allpackages = $(if $(__allpackages),,$(eval __allpackages := $$(_allpackages)))$(__allpackages)
|
||||
TESTDIRS ?= ./...
|
||||
|
||||
.PHONY: goimports
|
||||
goimports: $(gobin_tool)
|
||||
@@ -164,14 +155,38 @@ gox: $(gobin_tool)
|
||||
# We make gox globally available, for people to use by hand
|
||||
$(gobin_tool) $(gox)
|
||||
|
||||
Q := $(if $V,,@)
|
||||
|
||||
$(gobin_tool):
|
||||
GO111MODULE=off go get -u github.com/myitcv/gobin
|
||||
|
||||
authors:
|
||||
$Q git log --all --format='%aN <%cE>' | sort -u | sed -n '/github/!p' > GITAUTHORS
|
||||
$Q cat AUTHORS GITAUTHORS | sort -u > NEWAUTHORS
|
||||
$Q mv NEWAUTHORS AUTHORS
|
||||
$Q rm -f NEWAUTHORS
|
||||
$Q rm -f GITAUTHORS
|
||||
git log --all --format='%aN <%cE>' | sort -u | sed -n '/github/!p' > GITAUTHORS
|
||||
cat AUTHORS GITAUTHORS | sort -u > NEWAUTHORS
|
||||
mv NEWAUTHORS AUTHORS
|
||||
rm -f NEWAUTHORS
|
||||
rm -f GITAUTHORS
|
||||
|
||||
checksums_2.3.1.txt:
|
||||
curl -o checksums_2.3.1.txt -L https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.1/checksums.txt
|
||||
kubebuilder_2.3.1_${TEST_OS}_${TEST_ARCH}.tar.gz:
|
||||
curl -C - -O -L https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.1/kubebuilder_2.3.1_${TEST_OS}_${TEST_ARCH}.tar.gz
|
||||
kubebuilder_2.3.1_${TEST_OS}_${TEST_ARCH}: kubebuilder_2.3.1_${TEST_OS}_${TEST_ARCH}.tar.gz checksums_2.3.1.txt
|
||||
sha256sum -c --ignore-missing checksums_2.3.1.txt
|
||||
tar -xvf kubebuilder_2.3.1_${TEST_OS}_${TEST_ARCH}.tar.gz
|
||||
|
||||
.PHONY: envtest
|
||||
envtest: kubebuilder_2.3.1_${TEST_OS}_${TEST_ARCH}
|
||||
# You can add klog flags for debugging, like: -klog.v=10 -klog.logtostderr
|
||||
# klogv2 flags just wraps our existing logrus.
|
||||
KUBEBUILDER_ASSETS=$(PWD)/kubebuilder_2.3.1_${TEST_OS}_${TEST_ARCH}/bin $(GOTEST) -run=TestEnvtest ./node -envtest=true
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
goimports -w $(shell go list -f '{{.Dir}}' ./...)
|
||||
|
||||
|
||||
export GOLANG_CI_LINT_VERSION ?= v1.48.0
|
||||
DOCKER_BUILD ?= docker buildx build
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
$(DOCKER_BUILD) --target=lint --build-arg GOLANG_CI_LINT_VERSION --build-arg OUT_FORMAT .
|
||||
|
||||
12
Makefile.e2e
12
Makefile.e2e
@@ -1,7 +1,7 @@
|
||||
.PHONY: skaffold.validate
|
||||
skaffold.validate: kubectl_context := $(shell kubectl config current-context)
|
||||
skaffold.validate:
|
||||
if [[ ! "minikube,docker-for-desktop,docker-desktop" =~ .*"$(kubectl_context)".* ]]; then \
|
||||
@if [[ ! "minikube,docker-for-desktop,docker-desktop" =~ .*"$(kubectl_context)".* ]]; then \
|
||||
echo current-context is [$(kubectl_context)]. Must be one of [minikube,docker-for-desktop,docker-desktop]; \
|
||||
false; \
|
||||
fi
|
||||
@@ -11,7 +11,8 @@ skaffold.validate:
|
||||
# MODE must be set to one of "dev" (default), "delete" or "run", and is used as the skaffold command to be run.
|
||||
.PHONY: skaffold
|
||||
skaffold: MODE ?= dev
|
||||
skaffold: skaffold/$(MODE)
|
||||
.SECONDEXPANSION:
|
||||
skaffold: skaffold/$$(MODE)
|
||||
|
||||
.PHONY: skaffold/%
|
||||
skaffold/%: PROFILE := local
|
||||
@@ -20,6 +21,8 @@ skaffold/%: skaffold.validate
|
||||
-f $(PWD)/hack/skaffold/virtual-kubelet/skaffold.yml \
|
||||
-p $(PROFILE)
|
||||
|
||||
skaffold/run skaffold/dev: bin/e2e/virtual-kubelet
|
||||
|
||||
bin/e2e:
|
||||
@mkdir -p bin/e2e
|
||||
|
||||
@@ -36,10 +39,11 @@ e2e: NODE_NAME := vkubelet-mock-0
|
||||
e2e: export VK_BUILD_TAGS += mock_provider
|
||||
e2e: e2e.clean bin/e2e/virtual-kubelet skaffold/run
|
||||
@echo Running tests...
|
||||
cd $(PWD)/internal/test/e2e && go test -v -timeout 5m -tags e2e ./... \
|
||||
cd $(PWD)/internal/test/e2e && $(GOTEST) -timeout 5m -tags e2e ./... \
|
||||
-kubeconfig=$(KUBECONFIG) \
|
||||
-namespace=$(NAMESPACE) \
|
||||
-node-name=$(NODE_NAME) \
|
||||
-node-name=$(NODE_NAME)
|
||||
@$(MAKE) e2e.clean
|
||||
|
||||
.PHONY: e2e.clean
|
||||
e2e.clean: NODE_NAME ?= vkubelet-mock-0
|
||||
|
||||
306
README.md
306
README.md
@@ -1,17 +1,16 @@
|
||||
# Virtual Kubelet
|
||||
|
||||
[](https://pkg.go.dev/github.com/virtual-kubelet/virtual-kubelet)
|
||||
|
||||
Virtual Kubelet is an open source [Kubernetes kubelet](https://kubernetes.io/docs/reference/generated/kubelet/)
|
||||
implementation that masquerades as a kubelet for the purposes of connecting Kubernetes to other APIs.
|
||||
This allows the nodes to be backed by other services like ACI, AWS Fargate, [IoT Edge](https://github.com/Azure/iot-edge-virtual-kubelet-provider) etc. The primary scenario for VK is enabling the extension of the Kubernetes API into serverless container platforms like ACI and Fargate, though we are open to others. However, it should be noted that VK is explicitly not intended to be an alternative to Kubernetes federation.
|
||||
This allows the nodes to be backed by other services like ACI, AWS Fargate, [IoT Edge](https://github.com/Azure/iot-edge-virtual-kubelet-provider), [Tensile Kube](https://github.com/virtual-kubelet/tensile-kube) etc. The primary scenario for VK is enabling the extension of the Kubernetes API into serverless container platforms like ACI and Fargate, though we are open to others. However, it should be noted that VK is explicitly not intended to be an alternative to Kubernetes federation.
|
||||
|
||||
Virtual Kubelet features a pluggable architecture and direct use of Kubernetes primitives, making it much easier to build on.
|
||||
|
||||
We invite the Kubernetes ecosystem to join us in empowering developers to build
|
||||
upon our base. Join our slack channel named, virtual-kubelet, within the [Kubernetes slack group](https://kubernetes.slack.com/).
|
||||
|
||||
Please note this software is experimental and should not be used for anything
|
||||
resembling a production workload.
|
||||
|
||||
The best description is "Kubernetes API on top, programmable back."
|
||||
|
||||
#### Table of Contents
|
||||
@@ -19,12 +18,16 @@ The best description is "Kubernetes API on top, programmable back."
|
||||
* [How It Works](#how-it-works)
|
||||
* [Usage](#usage)
|
||||
* [Providers](#providers)
|
||||
+ [Admiralty Multi-Cluster Scheduler](#admiralty-multi-cluster-scheduler)
|
||||
+ [Alibaba Cloud ECI Provider](#alibaba-cloud-eci-provider)
|
||||
+ [Azure Container Instances Provider](#azure-container-instances-provider)
|
||||
+ [Azure Batch GPU Provider](https://github.com/virtual-kubelet/azure-batch/blob/master/README.md)
|
||||
+ [AWS Fargate Provider](#aws-fargate-provider)
|
||||
+ [Elotl Kip](#elotl-kip)
|
||||
+ [HashiCorp Nomad](#hashicorp-nomad-provider)
|
||||
+ [Liqo](#liqo-provider)
|
||||
+ [OpenStack Zun](#openstack-zun-provider)
|
||||
+ [Tensile Kube Provider](#tensile-kube-provider)
|
||||
+ [Adding a New Provider via the Provider Interface](#adding-a-new-provider-via-the-provider-interface)
|
||||
* [Testing](#testing)
|
||||
+ [Unit tests](#unit-tests)
|
||||
@@ -40,43 +43,14 @@ The diagram below illustrates how Virtual-Kubelet works.
|
||||
|
||||
## Usage
|
||||
|
||||
Deploy a Kubernetes cluster and make sure it's reachable.
|
||||
Virtual Kubelet is focused on providing a library that you can consume in your
|
||||
project to build a custom Kubernetes node agent.
|
||||
|
||||
### Outside the Kubernetes cluster
|
||||
See godoc for up to date instructions on consuming this project:
|
||||
https://godoc.org/github.com/virtual-kubelet/virtual-kubelet
|
||||
|
||||
Run the binary with your chosen provider:
|
||||
|
||||
```bash
|
||||
./bin/virtual-kubelet --provider <your provider>
|
||||
```
|
||||
|
||||
Now that the virtual-kubelet is deployed run `kubectl get nodes` and you should see
|
||||
a `virtual-kubelet` node.
|
||||
|
||||
### Inside the Kubernetes cluster (Minikube or Docker for Desktop)
|
||||
|
||||
It is possible to run the Virtual Kubelet as a Kubernetes pod inside a Minikube or Docker for Desktop cluster.
|
||||
As of this writing, automation of this deployment is supported only for the mock provider, and is primarily intended at testing.
|
||||
In order to deploy the Virtual Kubelet, you need to [install `skaffold`](https://github.com/GoogleContainerTools/skaffold#installation).
|
||||
You also need to make sure that your current context is either `minikube` or `docker-for-desktop`.
|
||||
|
||||
In order to deploy the Virtual Kubelet, run the following command after the prerequisites have been met:
|
||||
|
||||
```console
|
||||
$ make skaffold
|
||||
```
|
||||
|
||||
By default, this will run `skaffold` in [_development_ mode](https://github.com/GoogleContainerTools/skaffold#skaffold-dev).
|
||||
This will make `skaffold` watch `hack/skaffold/virtual-kubelet/Dockerfile` and its dependencies for changes and re-deploy the Virtual Kubelet when said changes happen.
|
||||
It will also make `skaffold` stream logs from the Virtual Kubelet pod.
|
||||
|
||||
As an alternative, and if you are not concerned about continuous deployment and log streaming, you can run the following command instead:
|
||||
|
||||
```console
|
||||
$ make skaffold MODE=run
|
||||
```
|
||||
|
||||
This will build and deploy the Virtual Kubelet, and return.
|
||||
There are implementations available for [several providers](#providers), see
|
||||
those repos for details on how to deploy.
|
||||
|
||||
## Current Features
|
||||
|
||||
@@ -89,34 +63,6 @@ This will build and deploy the Virtual Kubelet, and return.
|
||||
- bring your own virtual network
|
||||
|
||||
|
||||
## Command-Line Usage
|
||||
|
||||
```bash
|
||||
virtual-kubelet implements the Kubelet interface with a pluggable
|
||||
backend implementation allowing users to create kubernetes nodes without running the kubelet.
|
||||
This allows users to schedule kubernetes workloads on nodes that aren't running Kubernetes.
|
||||
|
||||
Usage:
|
||||
virtual-kubelet [flags]
|
||||
virtual-kubelet [command]
|
||||
|
||||
Available Commands:
|
||||
help Help about any command
|
||||
version Show the version of the program
|
||||
|
||||
Flags:
|
||||
-h, --help help for virtual-kubelet
|
||||
--kubeconfig string config file (default is $HOME/.kube/config)
|
||||
--namespace string kubernetes namespace (default is 'all')
|
||||
--nodename string kubernetes node name (default "virtual-kubelet")
|
||||
--os string Operating System (Linux/Windows) (default "Linux")
|
||||
--provider string cloud provider
|
||||
--provider-config string cloud provider configuration file
|
||||
--taint string apply taint to node, making scheduling explicit
|
||||
|
||||
Use "virtual-kubelet [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
## Providers
|
||||
|
||||
This project features a pluggable provider interface developers can implement
|
||||
@@ -133,6 +79,9 @@ Providers must provide the following functionality to be considered a supported
|
||||
2. Conforms to the current API provided by Virtual Kubelet.
|
||||
3. Does not have access to the Kubernetes API Server and has a well-defined callback mechanism for getting data like secrets or configmaps.
|
||||
|
||||
### Admiralty Multi-Cluster Scheduler
|
||||
|
||||
Admiralty Multi-Cluster Scheduler mutates annotated pods into "proxy pods" scheduled on a virtual-kubelet node and creates corresponding "delegate pods" in remote clusters (actually running the containers). A feedback loop updates the statuses and annotations of the proxy pods to reflect the statuses and annotations of the delegate pods. You can find more details in the [Admiralty Multi-Cluster Scheduler documentation](https://github.com/admiraltyio/multicluster-scheduler).
|
||||
|
||||
### Alibaba Cloud ECI Provider
|
||||
|
||||
@@ -159,10 +108,6 @@ You can find detailed instructions on how to set it up and how to test it in the
|
||||
The Azure connector can use a configuration file specified by the `--provider-config` flag.
|
||||
The config file is in TOML format, and an example lives in `providers/azure/example.toml`.
|
||||
|
||||
#### More Details
|
||||
|
||||
See the [ACI Readme](https://github.com/virtual-kubelet/alibabacloud-eci/blob/master/eci.toml)
|
||||
|
||||
### AWS Fargate Provider
|
||||
|
||||
[AWS Fargate](https://aws.amazon.com/fargate/) is a technology that allows you to run containers
|
||||
@@ -174,7 +119,13 @@ IP addresses to connect to the internet, private IP addresses to connect to your
|
||||
security groups, IAM roles, CloudWatch Logs and many other AWS services. Pods on Fargate can
|
||||
co-exist with pods on regular worker nodes in the same Kubernetes cluster.
|
||||
|
||||
Easy instructions and a sample configuration file is available in the [AWS Fargate provider documentation](https://github.com/virtual-kubelet/aws-fargate/blob/master/README.md).
|
||||
Easy instructions and a sample configuration file is available in the [AWS Fargate provider documentation](https://github.com/virtual-kubelet/aws-fargate). Please note that this provider is not currently supported.
|
||||
|
||||
### Elotl Kip
|
||||
|
||||
[Kip](https://github.com/elotl/kip) is a provider that runs pods in cloud instances, allowing a Kubernetes cluster to transparently scale workloads into a cloud. When a pod is scheduled onto the virtual node, Kip starts a right-sized cloud instance for the pod's workload and dispatches the pod onto the instance. When the pod is finished running, the cloud instance is terminated.
|
||||
|
||||
When workloads run on Kip, your cluster size naturally scales with the cluster workload, pods are strongly isolated from each other and the user is freed from managing worker nodes and strategically packing pods onto nodes.
|
||||
|
||||
### HashiCorp Nomad Provider
|
||||
|
||||
@@ -184,12 +135,14 @@ using the provider, pods that are scheduled on the virtual Nomad node
|
||||
registered on Kubernetes will run as jobs on Nomad clients as they
|
||||
would on a Kubernetes node.
|
||||
|
||||
```bash
|
||||
./bin/virtual-kubelet --provider="nomad"
|
||||
```
|
||||
|
||||
For detailed instructions, follow the guide [here](https://github.com/virtual-kubelet/nomad/blob/master/README.md).
|
||||
|
||||
### Liqo Provider
|
||||
|
||||
[Liqo](https://liqo.io) implements a provider for Virtual Kubelet designed to transparently offload pods and services to "peered" Kubernetes remote cluster. Liqo is capable of discovering neighbor clusters (using DNS, mDNS) and "peer" with them, or in other words, establish a relationship to share part of the cluster resources. When a cluster has established a peering, a new instance of the Liqo Virtual Kubelet is spawned to seamlessly extend the capacity of the cluster, by providing an abstraction of the resources of the remote cluster. The provider combined with the Liqo network fabric extends the cluster networking by enabling Pod-to-Pod traffic and multi-cluster east-west services, supporting endpoints on both clusters.
|
||||
|
||||
For detailed instruction, follow the guide [here](https://github.com/liqotech/liqo/blob/master/README.md)
|
||||
|
||||
### OpenStack Zun Provider
|
||||
|
||||
OpenStack [Zun](https://docs.openstack.org/zun/latest/) provider for Virtual Kubelet connects
|
||||
@@ -205,71 +158,126 @@ and bind-mount Cinder volumes into a path inside a pod's container.
|
||||
|
||||
For detailed instructions, follow the guide [here](https://github.com/virtual-kubelet/openstack-zun/blob/master/README.md).
|
||||
|
||||
### Tensile Kube Provider
|
||||
|
||||
[Tensile kube](https://github.com/virtual-kubelet/tensile-kube/blob/master/README.md) is contributed by [tencent
|
||||
games](https://game.qq.com), which is provider for Virtual Kubelet connects your Kubernetes cluster with other Kubernetes clusters. This provider enables us extending Kubernetes to an unlimited one. By using the provider, pods that are scheduled on the virtual node registered on Kubernetes will run as jobs on other Kubernetes clusters' nodes.
|
||||
|
||||
### Adding a New Provider via the Provider Interface
|
||||
|
||||
The structure we chose allows you to have all the power of the Kubernetes API
|
||||
on top with a pluggable interface.
|
||||
Providers consume this project as a library which implements the core logic of
|
||||
a Kubernetes node agent (Kubelet), and wire up their implementation for
|
||||
performing the neccessary actions.
|
||||
|
||||
Create a new directory for your provider under `providers` and implement the
|
||||
following interface. Then add register your provider in
|
||||
`providers/register/<provider_name>_provider.go`. Make sure to add a build tag so that
|
||||
your provider can be excluded from being built. The format for this build tag
|
||||
should be `no_<provider_name>_provider`. Also make sure your provider has all
|
||||
necessary platform build tags, e.g. "linux" if your provider only compiles on Linux.
|
||||
There are 3 main interfaces:
|
||||
|
||||
#### PodLifecylceHandler
|
||||
|
||||
When pods are created, updated, or deleted from Kubernetes, these methods are
|
||||
called to handle those actions.
|
||||
|
||||
[godoc#PodLifecylceHandler](https://godoc.org/github.com/virtual-kubelet/virtual-kubelet/node#PodLifecycleHandler)
|
||||
|
||||
```go
|
||||
// Provider contains the methods required to implement a virtual-kubelet provider.
|
||||
type Provider interface {
|
||||
// CreatePod takes a Kubernetes Pod and deploys it within the provider.
|
||||
CreatePod(ctx context.Context, pod *v1.Pod) error
|
||||
type PodLifecycleHandler interface {
|
||||
// CreatePod takes a Kubernetes Pod and deploys it within the provider.
|
||||
CreatePod(ctx context.Context, pod *corev1.Pod) error
|
||||
|
||||
// UpdatePod takes a Kubernetes Pod and updates it within the provider.
|
||||
UpdatePod(ctx context.Context, pod *v1.Pod) error
|
||||
// UpdatePod takes a Kubernetes Pod and updates it within the provider.
|
||||
UpdatePod(ctx context.Context, pod *corev1.Pod) error
|
||||
|
||||
// DeletePod takes a Kubernetes Pod and deletes it from the provider.
|
||||
DeletePod(ctx context.Context, pod *v1.Pod) error
|
||||
// DeletePod takes a Kubernetes Pod and deletes it from the provider.
|
||||
DeletePod(ctx context.Context, pod *corev1.Pod) error
|
||||
|
||||
// GetPod retrieves a pod by name from the provider (can be cached).
|
||||
GetPod(ctx context.Context, namespace, name string) (*v1.Pod, error)
|
||||
// GetPod retrieves a pod by name from the provider (can be cached).
|
||||
GetPod(ctx context.Context, namespace, name string) (*corev1.Pod, error)
|
||||
|
||||
// GetContainerLogs retrieves the logs of a container by name from the provider.
|
||||
GetContainerLogs(ctx context.Context, namespace, podName, containerName string, tail int) (string, error)
|
||||
// GetPodStatus retrieves the status of a pod by name from the provider.
|
||||
GetPodStatus(ctx context.Context, namespace, name string) (*corev1.PodStatus, error)
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error
|
||||
|
||||
// GetPodStatus retrieves the status of a pod by name from the provider.
|
||||
GetPodStatus(ctx context.Context, namespace, name string) (*v1.PodStatus, error)
|
||||
|
||||
// GetPods retrieves a list of all pods running on the provider (can be cached).
|
||||
GetPods(context.Context) ([]*v1.Pod, error)
|
||||
|
||||
// Capacity returns a resource list with the capacity constraints of the provider.
|
||||
Capacity(context.Context) v1.ResourceList
|
||||
|
||||
// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), which is
|
||||
// polled periodically to update the node status within Kubernetes.
|
||||
NodeConditions(context.Context) []v1.NodeCondition
|
||||
|
||||
// NodeAddresses returns a list of addresses for the node status
|
||||
// within Kubernetes.
|
||||
NodeAddresses(context.Context) []v1.NodeAddress
|
||||
|
||||
// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status
|
||||
// within Kubernetes.
|
||||
NodeDaemonEndpoints(context.Context) *v1.NodeDaemonEndpoints
|
||||
|
||||
// OperatingSystem returns the operating system the provider is for.
|
||||
OperatingSystem() string
|
||||
}
|
||||
|
||||
// PodMetricsProvider is an optional interface that providers can implement to expose pod stats
|
||||
type PodMetricsProvider interface {
|
||||
GetStatsSummary(context.Context) (*stats.Summary, error)
|
||||
// GetPods retrieves a list of all pods running on the provider (can be cached).
|
||||
GetPods(context.Context) ([]*corev1.Pod, error)
|
||||
}
|
||||
```
|
||||
|
||||
There is also an optional interface `PodNotifier` which enables the provider to
|
||||
asynchronously notify the virtual-kubelet about pod status changes. If this
|
||||
interface is not implemented, virtual-kubelet will periodically check the status
|
||||
of all pods.
|
||||
|
||||
It is highly recommended to implement `PodNotifier`, especially if you plan
|
||||
to run a large number of pods.
|
||||
|
||||
[godoc#PodNotifier](https://godoc.org/github.com/virtual-kubelet/virtual-kubelet/node#PodNotifier)
|
||||
|
||||
```go
|
||||
type PodNotifier interface {
|
||||
// NotifyPods instructs the notifier to call the passed in function when
|
||||
// the pod status changes.
|
||||
//
|
||||
// NotifyPods should not block callers.
|
||||
NotifyPods(context.Context, func(*corev1.Pod))
|
||||
}
|
||||
```
|
||||
|
||||
`PodLifecycleHandler` is consumed by the `PodController` which is the core
|
||||
logic for managing pods assigned to the node.
|
||||
|
||||
```go
|
||||
pc, _ := node.NewPodController(podControllerConfig) // <-- instatiates the pod controller
|
||||
pc.Run(ctx) // <-- starts watching for pods to be scheduled on the node
|
||||
```
|
||||
|
||||
#### NodeProvider
|
||||
|
||||
NodeProvider is responsible for notifying the virtual-kubelet about node status
|
||||
updates. Virtual-Kubelet will periodically check the status of the node and
|
||||
update Kubernetes accordingly.
|
||||
|
||||
[godoc#NodeProvider](https://godoc.org/github.com/virtual-kubelet/virtual-kubelet/node#NodeProvider)
|
||||
|
||||
```go
|
||||
type NodeProvider interface {
|
||||
// Ping checks if the node is still active.
|
||||
// This is intended to be lightweight as it will be called periodically as a
|
||||
// heartbeat to keep the node marked as ready in Kubernetes.
|
||||
Ping(context.Context) error
|
||||
|
||||
// NotifyNodeStatus is used to asynchronously monitor the node.
|
||||
// The passed in callback should be called any time there is a change to the
|
||||
// node's status.
|
||||
// This will generally trigger a call to the Kubernetes API server to update
|
||||
// the status.
|
||||
//
|
||||
// NotifyNodeStatus should not block callers.
|
||||
NotifyNodeStatus(ctx context.Context, cb func(*corev1.Node))
|
||||
}
|
||||
```
|
||||
|
||||
Virtual Kubelet provides a `NaiveNodeProvider` that you can use if you do not
|
||||
plan to have custom node behavior.
|
||||
|
||||
[godoc#NaiveNodeProvider](https://godoc.org/github.com/virtual-kubelet/virtual-kubelet/node#NaiveNodeProvider)
|
||||
|
||||
`NodeProvider` gets consumed by the `NodeController`, which is core logic for
|
||||
managing the node object in Kubernetes.
|
||||
|
||||
```go
|
||||
nc, _ := node.NewNodeController(nodeProvider, nodeSpec) // <-- instantiate a node controller from a node provider and a kubernetes node spec
|
||||
nc.Run(ctx) // <-- creates the node in kubernetes and starts up he controller
|
||||
```
|
||||
|
||||
#### API endpoints
|
||||
|
||||
One of the roles of a Kubelet is to accept requests from the API server for
|
||||
things like `kubectl logs` and `kubectl exec`. Helpers for setting this up are
|
||||
provided [here](https://godoc.org/github.com/virtual-kubelet/virtual-kubelet/node/api)
|
||||
|
||||
#### Scrape Pod metrics
|
||||
|
||||
If you want to use HPA(Horizontal Pod Autoscaler) in your cluster, the provider should implement the `GetStatsSummary` function. Then metrics-server will be able to get the metrics of the pods on virtual-kubelet. Otherwise, you may see `No metrics for pod ` on metrics-server, which means the metrics of the pods on virtual-kubelet are not collected.
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit tests
|
||||
@@ -278,43 +286,7 @@ Running the unit tests locally is as simple as `make test`.
|
||||
|
||||
### End-to-end tests
|
||||
|
||||
Virtual Kubelet includes an end-to-end (e2e) test suite which is used to validate its implementation.
|
||||
The current e2e suite **does not** run for any providers other than the `mock` provider.
|
||||
|
||||
To run the e2e suite, three things are required:
|
||||
- a local Kubernetes cluster (we have tested with [Docker for Mac](https://docs.docker.com/docker-for-mac/install/) and [Minikube](https://github.com/kubernetes/minikube));
|
||||
- Your _kubeconfig_ default context points to the local Kubernetes cluster;
|
||||
- [`skaffold`](https://github.com/GoogleContainerTools/skaffold).
|
||||
|
||||
Since our CI uses Minikube, we describe below how to run e2e on top of it.
|
||||
|
||||
To create a Minikube cluster, run the following command after [installing Minikube](https://github.com/kubernetes/minikube#installation):
|
||||
|
||||
```console
|
||||
$ minikube start
|
||||
```
|
||||
|
||||
The e2e suite requires Virtual Kubelet to be running as a pod inside the Kubernetes cluster.
|
||||
In order to make the testing process easier, the build toolchain leverages on `skaffold` to automatically deploy the Virtual Kubelet to the Kubernetes cluster using the mock provider.
|
||||
|
||||
To run the e2e test suite, you can run the following command:
|
||||
|
||||
```console
|
||||
$ make e2e
|
||||
```
|
||||
|
||||
When you're done testing, you can run the following command to cleanup the resources created by `skaffold`:
|
||||
|
||||
```console
|
||||
$ make skaffold MODE=delete
|
||||
```
|
||||
|
||||
Please note that this will not unregister the Virtual Kubelet as a node in the Kubernetes cluster.
|
||||
In order to do so, you should run:
|
||||
|
||||
```console
|
||||
$ kubectl delete node vkubelet-mock-0
|
||||
```
|
||||
Check out [`test/e2e`](./test/e2e) for more details.
|
||||
|
||||
## Known quirks and workarounds
|
||||
|
||||
@@ -335,8 +307,8 @@ Enable the ServiceNodeExclusion flag, by modifying the Controller Manager manife
|
||||
Virtual Kubelet follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
Sign the [CNCF CLA](https://github.com/kubernetes/community/blob/master/CLA.md) to be able to make Pull Requests to this repo.
|
||||
|
||||
Bi-weekly Virtual Kubelet Architecture meetings are held at 11am PST in this [zoom meeting room](https://zoom.us/j/245165908). Check out the calendar [here](https://calendar.google.com/calendar?cid=bjRtbGMxYWNtNXR0NXQ1a2hqZmRkNTRncGNAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ).
|
||||
Monthly Virtual Kubelet Office Hours are held at 10am PST on the last Thursday of every month in this [zoom meeting room](https://zoom.us/j/94701509915). Check out the calendar [here](https://calendar.google.com/calendar?cid=bjRtbGMxYWNtNXR0NXQ1a2hqZmRkNTRncGNAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ).
|
||||
|
||||
Our google drive with design specifications and meeting notes are [here](https://drive.google.com/drive/folders/19Ndu11WBCCBDowo9CrrGUHoIfd2L8Ueg?usp=sharing).
|
||||
|
||||
|
||||
We also have a community slack channel named virtual-kubelet in the Kubernetes slack. You can also connect with the Virtual Kubelet community via the [mailing list](https://lists.cncf.io/g/virtualkubelet-dev).
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# The Virtual Kubelet Helm chart
|
||||
|
||||
Each version of Virtual Kubelet has a dedicated [Helm](https://helm.sh) chart. Those charts are served as static assets directly from GitHub.
|
||||
|
||||
## The `index.yaml` file
|
||||
|
||||
This subdirectory has an `index.yaml` file, which is necessary for it to act as a Helm chart repository. To re-generate the `index.yaml` file (assuming that you have Helm installed):
|
||||
|
||||
```shell
|
||||
cd /path/to/virtual-kubelet
|
||||
helm repo index charts
|
||||
```
|
||||
|
||||
The `index.yaml` then needs to be committed to Git and merged to `master`.
|
||||
@@ -1,231 +0,0 @@
|
||||
apiVersion: v1
|
||||
entries:
|
||||
virtual-kubelet:
|
||||
- appVersion: "0.5"
|
||||
created: 2019-01-28T11:18:23.622097-08:00
|
||||
description: A Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
digest: b4af3cc07e1a914b862ebcd05facbf155b16e098a138fcd7f5dc8ac715aabfa2
|
||||
icon: https://avatars2.githubusercontent.com/u/34250142
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-0.5.0.tgz
|
||||
version: 0.5.0
|
||||
- appVersion: "0.5"
|
||||
created: 2019-01-28T11:18:23.62762-08:00
|
||||
description: A Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
digest: b4af3cc07e1a914b862ebcd05facbf155b16e098a138fcd7f5dc8ac715aabfa2
|
||||
icon: https://avatars2.githubusercontent.com/u/34250142
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-latest.tgz
|
||||
version: 0.5.0
|
||||
- appVersion: "0.4"
|
||||
created: 2019-01-28T11:18:23.62156-08:00
|
||||
description: A Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
digest: ae4b8be9d69129f1002ea2228848ec790ea1280d9ff0f7dd99d0d6e3e13922f2
|
||||
icon: https://avatars2.githubusercontent.com/u/34250142
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-0.4.0.tgz
|
||||
version: 0.4.0
|
||||
- appVersion: "0.3"
|
||||
created: 2019-01-28T11:18:23.620905-08:00
|
||||
description: A Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
digest: f472f181c0724420d4f12218f8fc7f65d09fa02a8292f418d1537bf71231d643
|
||||
icon: https://avatars2.githubusercontent.com/u/34250142
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-0.3.0.tgz
|
||||
version: 0.3.0
|
||||
- appVersion: "0.3"
|
||||
created: 2019-01-28T11:18:23.6202-08:00
|
||||
description: A Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
digest: 5a2d0a269620ffe49498686161e853f49761ca4f905577fe1b8e552ab85fcaca
|
||||
icon: https://avatars2.githubusercontent.com/u/34250142
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-0.2.0.tgz
|
||||
version: 0.2.0
|
||||
- created: 2019-01-28T11:18:23.619614-08:00
|
||||
description: a Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
digest: be2778949548cfb0cebe638cbe044d89045eb34ffc8d62180b86ff2f0f30b323
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-0.1.3.tgz
|
||||
version: 0.1.3
|
||||
- created: 2019-01-28T11:18:23.618974-08:00
|
||||
description: a Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
digest: b8519c66766d06a68671a2b8940cf44cccdf9321f144dead543f62d16325331e
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-0.1.2.tgz
|
||||
version: 0.1.2
|
||||
- created: 2019-01-28T11:18:23.618406-08:00
|
||||
description: a Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
digest: b15b3d5acde2cc264b5e8624c3bafcc249440c662ae353ef7012493eb0b6abf6
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-0.1.1.tgz
|
||||
version: 0.1.1
|
||||
- created: 2019-01-28T11:18:23.617865-08:00
|
||||
description: a Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
digest: 22c60ad2f7ea71abf58d65e52b91c2b9feca1ad6a15091321f7f12e5c43ecf81
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-0.1.0.tgz
|
||||
version: 0.1.0
|
||||
virtual-kubelet-for-aks:
|
||||
- created: 2019-01-28T11:18:23.622632-08:00
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
digest: 345cda5aeb537129e1ecc3d23c0cf47651454f672694a4402a7bbb5d3f3a91ba
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet-for-aks
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-for-aks-0.1.10.tgz
|
||||
version: 0.1.10
|
||||
- created: 2019-01-28T11:18:23.627017-08:00
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
digest: 345cda5aeb537129e1ecc3d23c0cf47651454f672694a4402a7bbb5d3f3a91ba
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet-for-aks
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-for-aks-latest.tgz
|
||||
version: 0.1.10
|
||||
- created: 2019-01-28T11:18:23.626524-08:00
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
digest: 4a30821657ff4ef4522060c9905e5659656c035126a7a9e650dd9bb54621b9eb
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet-for-aks
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-for-aks-0.1.9.tgz
|
||||
version: 0.1.9
|
||||
- created: 2019-01-28T11:18:23.626081-08:00
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
digest: 18d988bfb4d24b3674c6c690c0445b85cf3969471cfce74f7c49e87483cb497d
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet-for-aks
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-for-aks-0.1.8.tgz
|
||||
version: 0.1.8
|
||||
- created: 2019-01-28T11:18:23.625442-08:00
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
digest: 21c0719fe330981a170810ee4c3e84375106c176de08894827c8208a66f52112
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet-for-aks
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-for-aks-0.1.7.tgz
|
||||
version: 0.1.7
|
||||
- created: 2019-01-28T11:18:23.624524-08:00
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
digest: b5e72d03f04113a46350fa800135a67f5af9b4ffe3d6650bdeebd271b885e5aa
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet-for-aks
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-for-aks-0.1.6.tgz
|
||||
version: 0.1.6
|
||||
- created: 2019-01-28T11:18:23.624071-08:00
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
digest: 5b04abcc63dcc71b5ad25c8368ad1b114accf721e9c475c5a7f962a48fb0ed65
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet-for-aks
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-for-aks-0.1.5.tgz
|
||||
version: 0.1.5
|
||||
- created: 2019-01-28T11:18:23.623547-08:00
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
digest: b639126041dc4f3d0307f6a678d22021ba149f28612eb0bfc3903c75cf1ea414
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet-for-aks
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-for-aks-0.1.4.tgz
|
||||
version: 0.1.4
|
||||
- created: 2019-01-28T11:18:23.623097-08:00
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
digest: 1b428fd99c667482681c83b61753e7327b85ec2644ef4fa0e9366492a0e73cd7
|
||||
maintainers:
|
||||
- email: junjiez@microsoft.com
|
||||
name: Robbie Zhang
|
||||
name: virtual-kubelet-for-aks
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
urls:
|
||||
- virtual-kubelet-for-aks-0.1.3.tgz
|
||||
version: 0.1.3
|
||||
generated: 2019-01-28T11:18:23.615432-08:00
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,8 +0,0 @@
|
||||
name: virtual-kubelet-for-aks
|
||||
version: 0.1.10
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
maintainers:
|
||||
- name: Robbie Zhang
|
||||
email: junjiez@microsoft.com
|
||||
@@ -1,12 +0,0 @@
|
||||
The virtual kubelet is getting deployed on your cluster.
|
||||
|
||||
To verify that virtual kubelet has started, run:
|
||||
|
||||
kubectl --namespace={{ .Release.Namespace }} get pods -l "app={{ template "fullname" . }}"
|
||||
|
||||
{{- if (not .Values.env.apiserverCert) and (not .Values.env.apiserverKey) }}
|
||||
|
||||
Note:
|
||||
TLS key pair not provided for VK HTTP listener. A key pair was generated for you. This generated key pair is not suitable for production use.
|
||||
|
||||
{{- end }}
|
||||
@@ -1,16 +0,0 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 24 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
@@ -1,14 +0,0 @@
|
||||
{{ if .Values.rbac.install }}
|
||||
apiVersion: "rbac.authorization.k8s.io/{{ .Values.rbac.apiVersion }}"
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ template "fullname" . }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ template "fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ .Values.rbac.roleRef }}
|
||||
{{ end }}
|
||||
@@ -1,79 +0,0 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ template "fullname" . }}
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ template "fullname" . }}
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ template "fullname" . }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
env:
|
||||
- name: KUBELET_PORT
|
||||
value: "10250"
|
||||
- name: ACS_CREDENTIAL_LOCATION
|
||||
value: /etc/acs/azure.json
|
||||
- name: AZURE_TENANT_ID
|
||||
value: {{ .Values.env.azureTenantId }}
|
||||
- name: AZURE_SUBSCRIPTION_ID
|
||||
value: {{ .Values.env.azureSubscriptionId }}
|
||||
- name: AZURE_CLIENT_ID
|
||||
value: {{ .Values.env.azureClientId }}
|
||||
- name: AZURE_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "fullname" . }}
|
||||
key: clientSecret
|
||||
- name: ACI_RESOURCE_GROUP
|
||||
value: {{ .Values.env.aciResourceGroup }}
|
||||
- name: ACI_REGION
|
||||
value: {{ default "westus" .Values.env.aciRegion }}
|
||||
- name: APISERVER_CERT_LOCATION
|
||||
value: /etc/virtual-kubelet/cert.pem
|
||||
- name: APISERVER_KEY_LOCATION
|
||||
value: /etc/virtual-kubelet/key.pem
|
||||
- name: VKUBELET_POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: ACI_EXTRA_USER_AGENT
|
||||
value: {{ printf "helm-chart/aks/%s/%s" .Chart.Name .Chart.Version }}
|
||||
- name: ACI_SUBNET_NAME
|
||||
value: {{ .Values.env.aciVnetSubnetName }}
|
||||
- name: ACI_SUBNET_CIDR
|
||||
value: {{ .Values.env.aciVnetSubnetCIDR }}
|
||||
- name: MASTER_URI
|
||||
value: {{ .Values.env.masterUri }}
|
||||
- name: CLUSTER_CIDR
|
||||
value: {{ .Values.env.clusterCIDR }}
|
||||
- name: KUBE_DNS_IP
|
||||
value: {{ .Values.env.kubeDnsIP }}
|
||||
{{ if .Values.loganalytics.enabled }}
|
||||
- name: LOG_ANALYTICS_AUTH_LOCATION
|
||||
value: /etc/virtual-kubelet/loganalytics.json
|
||||
{{ end }}
|
||||
volumeMounts:
|
||||
- name: credentials
|
||||
mountPath: "/etc/virtual-kubelet"
|
||||
- name: acs-credential
|
||||
mountPath: "/etc/acs/azure.json"
|
||||
command: ["virtual-kubelet"]
|
||||
args: ["--provider", "azure", "--namespace", {{ default "" .Values.env.monitoredNamespace | quote }}, "--nodename", {{ default "virtual-kubelet" .Values.env.nodeName | quote }} , "--os", {{ default "Linux" .Values.env.nodeOsType | quote }} ]
|
||||
volumes:
|
||||
- name: credentials
|
||||
secret:
|
||||
secretName: {{ template "fullname" . }}
|
||||
- name: acs-credential
|
||||
hostPath:
|
||||
path: /etc/kubernetes/azure.json
|
||||
type: File
|
||||
{{ if .Values.rbac.install }}
|
||||
serviceAccountName: {{ template "fullname" . }}
|
||||
{{ end }}
|
||||
nodeSelector:
|
||||
beta.kubernetes.io/os: linux
|
||||
@@ -1,22 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ template "fullname" . }}
|
||||
type: Opaque
|
||||
data:
|
||||
{{- if (not .Values.env.apiserverCert) and (not .Values.env.apiserverKey) }}
|
||||
{{- $ca := genCA "virtual-kubelet-ca" 3650 }}
|
||||
{{- $cn := printf "%s-virtual-kubelet-apiserver" .Release.Name }}
|
||||
{{- $altName1 := printf "%s-virtual-kubelet-apiserver.%s" .Release.Name .Release.Namespace }}
|
||||
{{- $altName2 := printf "%s-virtual-kubelet-apiserver.%s.svc" .Release.Name .Release.Namespace }}
|
||||
{{- $cert := genSignedCert $cn nil (list $altName1 $altName2) 3650 $ca }}
|
||||
cert.pem: {{ b64enc $cert.Cert }}
|
||||
key.pem: {{ b64enc $cert.Key }}
|
||||
{{ else }}
|
||||
cert.pem: {{ quote .Values.env.apiserverCert }}
|
||||
key.pem: {{ quote .Values.env.apiserverKey }}
|
||||
{{ end}}
|
||||
clientSecret: {{ default "" .Values.env.azureClientKey | b64enc | quote }}
|
||||
{{ if .Values.loganalytics.enabled }}
|
||||
loganalytics.json: {{ printf "{\"workspaceID\": \"%s\",\"workspaceKey\": \"%s\"}" (required "workspaceID is required for loganalytics" .Values.loganalytics.workspaceID ) (required "workspaceKey is required for loganalytics" .Values.loganalytics.workspaceKey ) | b64enc | quote }}
|
||||
{{ end }}
|
||||
@@ -1,6 +0,0 @@
|
||||
{{ if .Values.rbac.install }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ template "fullname" . }}
|
||||
{{ end }}
|
||||
@@ -1,34 +0,0 @@
|
||||
image:
|
||||
repository: microsoft/virtual-kubelet
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
env:
|
||||
azureClientId:
|
||||
azureClientKey:
|
||||
azureTenantId:
|
||||
azureSubscriptionId:
|
||||
aciResourceGroup:
|
||||
aciRegion:
|
||||
nodeName:
|
||||
nodeTaint:
|
||||
nodeOsType:
|
||||
apiserverCert:
|
||||
apiserverKey:
|
||||
monitoredNamespace:
|
||||
aciVnetSubnetName:
|
||||
aciVnetSubnetCidr:
|
||||
masterUri:
|
||||
clusterCidr:
|
||||
kubeDnsIp:
|
||||
loganalytics:
|
||||
enabled: false
|
||||
workspaceID:
|
||||
workspaceKey:
|
||||
|
||||
# Install Default RBAC roles and bindings
|
||||
rbac:
|
||||
install: true
|
||||
## RBAC api version
|
||||
apiVersion: v1beta1
|
||||
# Cluster role reference
|
||||
roleRef: cluster-admin
|
||||
Binary file not shown.
@@ -1,10 +0,0 @@
|
||||
name: virtual-kubelet
|
||||
version: 0.5.0
|
||||
appVersion: 0.5
|
||||
description: A Helm chart to install virtual kubelet inside a Kubernetes cluster.
|
||||
icon: https://avatars2.githubusercontent.com/u/34250142
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
maintainers:
|
||||
- name: Robbie Zhang
|
||||
email: junjiez@microsoft.com
|
||||
@@ -1,12 +0,0 @@
|
||||
The virtual kubelet is getting deployed on your cluster.
|
||||
|
||||
To verify that virtual kubelet has started, run:
|
||||
|
||||
kubectl --namespace={{ .Release.Namespace }} get pods -l "app={{ template "vk.name" . }}"
|
||||
|
||||
{{- if (not .Values.apiserverCert) and (not .Values.apiserverKey) }}
|
||||
|
||||
Note:
|
||||
TLS key pair not provided for VK HTTP listener. A key pair was generated for you. This generated key pair is not suitable for production use.
|
||||
|
||||
{{- end }}
|
||||
@@ -1,29 +0,0 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "vk.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 24 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "vk.fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Standard labels for helm resources
|
||||
*/}}
|
||||
{{- define "vk.labels" -}}
|
||||
labels:
|
||||
heritage: "{{ .Release.Service }}"
|
||||
release: "{{ .Release.Name }}"
|
||||
revision: "{{ .Release.Revision }}"
|
||||
chart: "{{ .Chart.Name }}"
|
||||
chartVersion: "{{ .Chart.Version }}"
|
||||
app: {{ template "vk.name" . }}
|
||||
{{- end -}}
|
||||
@@ -1,15 +0,0 @@
|
||||
{{ if .Values.rbac.install }}
|
||||
apiVersion: "rbac.authorization.k8s.io/{{ .Values.rbac.apiVersion }}"
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ template "vk.fullname" . }}
|
||||
{{ include "vk.labels" . | indent 2 }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ template "vk.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ .Values.rbac.roleRef }}
|
||||
{{ end }}
|
||||
@@ -1,159 +0,0 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ template "vk.fullname" . }}
|
||||
{{ include "vk.labels" . | indent 2 }}
|
||||
component: kubelet
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
{{ include "vk.labels" . | indent 6 }}
|
||||
component: kubelet
|
||||
annotations:
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
spec:
|
||||
containers:
|
||||
{{- if eq .Values.trace.exporter "jaeger" }}
|
||||
{{- with .Values.traceExporters.jaeger }}
|
||||
{{- if eq .endpoint "" }}
|
||||
- name: {{ tpl .name $ }}
|
||||
image: {{ .image.repository}}:{{.image.tag}}
|
||||
imagePullPolicy: {{ .image.pullPolicy }}
|
||||
ports:
|
||||
- containerPort: 16686
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
- name: {{ template "vk.fullname" . }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
env:
|
||||
- name: KUBELET_PORT
|
||||
value: "10250"
|
||||
- name: APISERVER_CERT_LOCATION
|
||||
value: /etc/virtual-kubelet/cert.pem
|
||||
- name: APISERVER_KEY_LOCATION
|
||||
value: /etc/virtual-kubelet/key.pem
|
||||
- name: VKUBELET_POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: VKUBELET_TAINT_KEY
|
||||
value: {{ .Values.taint.key }}
|
||||
- name: VKUBELET_TAINT_VALUE
|
||||
value: {{ tpl .Values.taint.value $ }}
|
||||
- name: VKUBELET_TAINT_EFFECT
|
||||
value: {{ .Values.taint.effect }}
|
||||
{{- if eq (required "You must specify a Virtual Kubelet provider" .Values.provider) "azure" }}
|
||||
{{- with .Values.providers.azure }}
|
||||
{{- if .loganalytics.enabled }}
|
||||
- name: LOG_ANALYTICS_AUTH_LOCATION
|
||||
value: /etc/virtual-kubelet/loganalytics.json
|
||||
- name: CLUSTER_RESOURCE_ID
|
||||
value: {{ .loganalytics.clusterResourceId }}
|
||||
{{- end }}
|
||||
{{- if .targetAKS }}
|
||||
- name: ACS_CREDENTIAL_LOCATION
|
||||
value: /etc/acs/azure.json
|
||||
- name: AZURE_TENANT_ID
|
||||
value: {{ .tenantId }}
|
||||
- name: AZURE_SUBSCRIPTION_ID
|
||||
value: {{ .subscriptionId }}
|
||||
- name: AZURE_CLIENT_ID
|
||||
value: {{ .clientId }}
|
||||
- name: AZURE_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "vk.fullname" $ }}
|
||||
key: clientSecret
|
||||
- name: ACI_RESOURCE_GROUP
|
||||
value: {{ .aciResourceGroup }}
|
||||
- name: ACI_REGION
|
||||
value: {{ .aciRegion }}
|
||||
- name: ACI_EXTRA_USER_AGENT
|
||||
value: {{ printf "helm-chart/aks/%s/%s" $.Chart.Name $.Chart.Version }}
|
||||
{{- else }}
|
||||
- name: AZURE_AUTH_LOCATION
|
||||
value: /etc/virtual-kubelet/credentials.json
|
||||
- name: ACI_RESOURCE_GROUP
|
||||
value: {{ required "aciResourceGroup is required" .aciResourceGroup }}
|
||||
- name: ACI_REGION
|
||||
value: {{ required "aciRegion is required" .aciRegion }}
|
||||
- name: ACI_EXTRA_USER_AGENT
|
||||
value: {{ printf "helm-chart/other/%s/%s" $.Chart.Name $.Chart.Version }}
|
||||
{{- end }}
|
||||
{{- if .vnet.enabled }}
|
||||
- name: ACI_SUBNET_NAME
|
||||
value: {{ required "subnetName is required" .vnet.subnetName }}
|
||||
- name: ACI_SUBNET_CIDR
|
||||
value: {{ .vnet.subnetCidr }}
|
||||
- name: MASTER_URI
|
||||
value: {{ required "masterUri is required" .masterUri }}
|
||||
- name: CLUSTER_CIDR
|
||||
value: {{ required "clusterCidr is required" .vnet.clusterCidr }}
|
||||
- name: KUBE_DNS_IP
|
||||
value: {{ required "kubeDnsIp is required" .vnet.kubeDnsIp }}
|
||||
{{- else }}
|
||||
- name: MASTER_URI
|
||||
value: {{ .masterUri }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if eq .Values.trace.exporter "jaeger" }}
|
||||
- name: JAEGER_ENDPOINT
|
||||
{{- with .Values.traceExporters.jaeger }}
|
||||
{{- if eq .endpoint "" }}
|
||||
value: "http://127.0.0.1:14268"
|
||||
{{- else }}
|
||||
value: {{.endpoint}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: credentials
|
||||
mountPath: "/etc/virtual-kubelet"
|
||||
{{- if eq (required "You must specify a Virtual Kubelet provider" .Values.provider) "azure" }}
|
||||
{{- if .Values.providers.azure.targetAKS }}
|
||||
- name: acs-credential
|
||||
mountPath: "/etc/acs/azure.json"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
command: ["virtual-kubelet"]
|
||||
args: [
|
||||
{{- if not .Values.taint.enabled }}
|
||||
"--disable-taint", "true",
|
||||
{{- end }}
|
||||
"--provider", "{{ required "You must specify a Virtual Kubelet provider" .Values.provider }}",
|
||||
"--namespace", "{{ .Values.monitoredNamespace }}",
|
||||
"--nodename", "{{ required "nodeName is required" .Values.nodeName }}",
|
||||
{{- if .Values.logLevel }}
|
||||
"--log-level", "{{.Values.logLevel}}",
|
||||
{{- end }}
|
||||
{{- if ne .Values.trace.exporter "" }}
|
||||
"--trace-exporter", "{{ .Values.trace.exporter }}",
|
||||
{{- if gt .Values.trace.sampleRate 0.0 }}
|
||||
"--trace-sample-rate", "{{ .Values.trace.sampleRate }}",
|
||||
{{- end }}
|
||||
{{- $serviceName := tpl .Values.trace.serviceName $ }}
|
||||
{{- if ne $serviceName "" }}
|
||||
"--trace-service-name", "{{ $serviceName }}",
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
"--os", "{{ .Values.nodeOsType }}"
|
||||
]
|
||||
volumes:
|
||||
- name: credentials
|
||||
secret:
|
||||
secretName: {{ template "vk.fullname" . }}
|
||||
{{- if eq (required "You must specify a Virtual Kubelet provider" .Values.provider) "azure" }}
|
||||
{{- if .Values.providers.azure.targetAKS }}
|
||||
- name: acs-credential
|
||||
hostPath:
|
||||
path: /etc/kubernetes/azure.json
|
||||
type: File
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ if .Values.rbac.install }} "{{ template "vk.fullname" . }}" {{ end }}
|
||||
nodeSelector:
|
||||
beta.kubernetes.io/os: linux
|
||||
@@ -1,31 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ template "vk.fullname" . }}
|
||||
{{ include "vk.labels" . | indent 2 }}
|
||||
type: Opaque
|
||||
data:
|
||||
{{- if (not .Values.apiserverCert) and (not .Values.apiserverKey) }}
|
||||
{{- $ca := genCA "virtual-kubelet-ca" 3650 }}
|
||||
{{- $cn := printf "%s-virtual-kubelet-apiserver" .Release.Name }}
|
||||
{{- $altName1 := printf "%s-virtual-kubelet-apiserver.%s" .Release.Name .Release.Namespace }}
|
||||
{{- $altName2 := printf "%s-virtual-kubelet-apiserver.%s.svc" .Release.Name .Release.Namespace }}
|
||||
{{- $cert := genSignedCert $cn nil (list $altName1 $altName2) 3650 $ca }}
|
||||
cert.pem: {{ b64enc $cert.Cert }}
|
||||
key.pem: {{ b64enc $cert.Key }}
|
||||
{{- else }}
|
||||
cert.pem: {{ quote .Values.apiserverCert }}
|
||||
key.pem: {{ quote .Values.apiserverKey }}
|
||||
{{- end }}
|
||||
{{- if eq (required "You must specify a Virtual Kubelet provider" .Values.provider) "azure" }}
|
||||
{{- with .Values.providers.azure }}
|
||||
{{- if .loganalytics.enabled }}
|
||||
loganalytics.json: {{ printf "{\"workspaceID\": \"%s\",\"workspaceKey\": \"%s\"}" (required "workspaceId is required for loganalytics" .loganalytics.workspaceId ) (required "workspaceKey is required for loganalytics" .loganalytics.workspaceKey ) | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- if .targetAKS }}
|
||||
clientSecret: {{ default "" .clientKey | b64enc | quote }}
|
||||
{{- else }}
|
||||
credentials.json: {{ printf "{ \"clientId\": \"%s\", \"clientSecret\": \"%s\", \"subscriptionId\": \"%s\", \"tenantId\": \"%s\", \"activeDirectoryEndpointUrl\": \"https://login.microsoftonline.com/\", \"resourceManagerEndpointUrl\": \"https://management.azure.com/\", \"activeDirectoryGraphResourceId\": \"https://graph.windows.net/\", \"sqlManagementEndpointUrl\": \"database.windows.net\", \"galleryEndpointUrl\": \"https://gallery.azure.com/\", \"managementEndpointUrl\": \"https://management.core.windows.net/\" }" (default "MISSING" .clientId) (default "MISSING" .clientKey) (default "MISSING" .subscriptionId) (default "MISSING" .tenantId) | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -1,7 +0,0 @@
|
||||
{{ if .Values.rbac.install }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ template "vk.fullname" . }}
|
||||
{{ include "vk.labels" . | indent 2 }}
|
||||
{{ end }}
|
||||
@@ -1,30 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ .Release.Name }}-{{ .Release.Revision }}-test"
|
||||
{{ include "vk.labels" . | indent 2 }}
|
||||
component: test
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
spec:
|
||||
containers:
|
||||
- image: hello-world:linux
|
||||
imagePullPolicy: Always
|
||||
name: helloworld
|
||||
resources:
|
||||
requests:
|
||||
memory: "0.1G"
|
||||
cpu: 10m
|
||||
limits:
|
||||
memory: "0.1G"
|
||||
cpu: 10m
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: "{{ .Values.nodeName }}"
|
||||
restartPolicy: Never
|
||||
tolerations:
|
||||
{{- if .Values.taint.enabled }}
|
||||
- key: "{{ .Values.taint.key }}"
|
||||
value: "{{ tpl .Values.taint.value $ }}"
|
||||
effect: "{{ .Values.taint.effect }}"
|
||||
{{- end }}
|
||||
@@ -1,65 +0,0 @@
|
||||
image:
|
||||
repository: microsoft/virtual-kubelet
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
|
||||
nodeName: "virtual-kubelet"
|
||||
nodeOsType: "Linux"
|
||||
monitoredNamespace: ""
|
||||
apiserverCert:
|
||||
apiserverKey:
|
||||
logLevel:
|
||||
|
||||
taint:
|
||||
enabled: true
|
||||
key: virtual-kubelet.io/provider
|
||||
value: "{{ .Values.provider }}"
|
||||
## `effect` must be `NoSchedule`, `PreferNoSchedule` or `NoExecute`.
|
||||
effect: NoSchedule
|
||||
|
||||
trace:
|
||||
exporter: ""
|
||||
serviceName: "{{ .Values.nodeName }}"
|
||||
sampleRate: 0
|
||||
|
||||
traceExporters:
|
||||
jaeger:
|
||||
name: "{{ .Values.trace.exporter }}"
|
||||
endpoint: ""
|
||||
image:
|
||||
repository: jaegertracing/all-in-one
|
||||
tag: 1.8
|
||||
pullPolicy: Always
|
||||
|
||||
providers:
|
||||
azure:
|
||||
## Set to true if deploying to Azure Kubernetes Service (AKS), otherwise false
|
||||
targetAKS: true
|
||||
clientId:
|
||||
clientKey:
|
||||
tenantId:
|
||||
subscriptionId:
|
||||
## `aciResourceGroup` and `aciRegion` are required only for non-AKS deployments
|
||||
aciResourceGroup:
|
||||
aciRegion:
|
||||
masterUri:
|
||||
loganalytics:
|
||||
enabled: false
|
||||
workspaceId:
|
||||
workspaceKey:
|
||||
clusterResourceId:
|
||||
vnet:
|
||||
enabled: false
|
||||
subnetName:
|
||||
subnetCidr:
|
||||
clusterCidr:
|
||||
kubeDnsIp:
|
||||
|
||||
## Install Default RBAC roles and bindings
|
||||
rbac:
|
||||
install: true
|
||||
serviceAccountName: virtual-kubelet
|
||||
## RBAC api version
|
||||
apiVersion: v1beta1
|
||||
## Cluster role reference
|
||||
roleRef: cluster-admin
|
||||
@@ -1,161 +0,0 @@
|
||||
// Copyright © 2017 The virtual-kubelet authors
|
||||
//
|
||||
// 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 root
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/node/api"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers"
|
||||
)
|
||||
|
||||
// AcceptedCiphers is the list of accepted TLS ciphers, with known weak ciphers elided
|
||||
// Note this list should be a moving target.
|
||||
var AcceptedCiphers = []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
}
|
||||
|
||||
func loadTLSConfig(certPath, keyPath string) (*tls.Config, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error loading tls certs")
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: AcceptedCiphers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setupHTTPServer(ctx context.Context, p providers.Provider, cfg *apiServerConfig) (_ func(), retErr error) {
|
||||
var closers []io.Closer
|
||||
cancel := func() {
|
||||
for _, c := range closers {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
if cfg.CertPath == "" || cfg.KeyPath == "" {
|
||||
log.G(ctx).
|
||||
WithField("certPath", cfg.CertPath).
|
||||
WithField("keyPath", cfg.KeyPath).
|
||||
Error("TLS certificates not provided, not setting up pod http server")
|
||||
} else {
|
||||
tlsCfg, err := loadTLSConfig(cfg.CertPath, cfg.KeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := tls.Listen("tcp", cfg.Addr, tlsCfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error setting up listener for pod http server")
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
podRoutes := api.PodHandlerConfig{
|
||||
RunInContainer: p.RunInContainer,
|
||||
GetContainerLogs: p.GetContainerLogs,
|
||||
GetPods: p.GetPods,
|
||||
}
|
||||
api.AttachPodRoutes(podRoutes, mux, true)
|
||||
|
||||
s := &http.Server{
|
||||
Handler: mux,
|
||||
TLSConfig: tlsCfg,
|
||||
}
|
||||
go serveHTTP(ctx, s, l, "pods")
|
||||
closers = append(closers, s)
|
||||
}
|
||||
|
||||
if cfg.MetricsAddr == "" {
|
||||
log.G(ctx).Info("Pod metrics server not setup due to empty metrics address")
|
||||
} else {
|
||||
l, err := net.Listen("tcp", cfg.MetricsAddr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not setup listener for pod metrics http server")
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
var summaryHandlerFunc api.PodStatsSummaryHandlerFunc
|
||||
if mp, ok := p.(providers.PodMetricsProvider); ok {
|
||||
summaryHandlerFunc = mp.GetStatsSummary
|
||||
}
|
||||
podMetricsRoutes := api.PodMetricsConfig{
|
||||
GetStatsSummary: summaryHandlerFunc,
|
||||
}
|
||||
api.AttachPodMetricsRoutes(podMetricsRoutes, mux)
|
||||
s := &http.Server{
|
||||
Handler: mux,
|
||||
}
|
||||
go serveHTTP(ctx, s, l, "pod metrics")
|
||||
closers = append(closers, s)
|
||||
}
|
||||
|
||||
return cancel, nil
|
||||
}
|
||||
|
||||
func serveHTTP(ctx context.Context, s *http.Server, l net.Listener, name string) {
|
||||
if err := s.Serve(l); err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
log.G(ctx).WithError(err).Errorf("Error setting up %s http server", name)
|
||||
}
|
||||
}
|
||||
l.Close()
|
||||
}
|
||||
|
||||
type apiServerConfig struct {
|
||||
CertPath string
|
||||
KeyPath string
|
||||
Addr string
|
||||
MetricsAddr string
|
||||
}
|
||||
|
||||
func getAPIConfig(c Opts) (*apiServerConfig, error) {
|
||||
config := apiServerConfig{
|
||||
CertPath: os.Getenv("APISERVER_CERT_LOCATION"),
|
||||
KeyPath: os.Getenv("APISERVER_KEY_LOCATION"),
|
||||
}
|
||||
|
||||
config.Addr = fmt.Sprintf(":%d", c.ListenPort)
|
||||
config.MetricsAddr = c.MetricsAddr
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
// Copyright © 2017 The virtual-kubelet authors
|
||||
//
|
||||
// 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 root
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/errdefs"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/node"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/kubernetes/typed/coordination/v1beta1"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/tools/record"
|
||||
)
|
||||
|
||||
// NewCommand creates a new top-level command.
|
||||
// This command is used to start the virtual-kubelet daemon
|
||||
func NewCommand(ctx context.Context, name string, s *providers.Store, c Opts) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: name,
|
||||
Short: name + " provides a virtual kubelet interface for your kubernetes cluster.",
|
||||
Long: name + ` implements the Kubelet interface with a pluggable
|
||||
backend implementation allowing users to create kubernetes nodes without running the kubelet.
|
||||
This allows users to schedule kubernetes workloads on nodes that aren't running Kubernetes.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runRootCommand(ctx, s, c)
|
||||
},
|
||||
}
|
||||
|
||||
installFlags(cmd.Flags(), &c)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRootCommand(ctx context.Context, s *providers.Store, c Opts) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
if ok := providers.ValidOperatingSystems[c.OperatingSystem]; !ok {
|
||||
return errdefs.InvalidInputf("operating system %q is not supported", c.OperatingSystem)
|
||||
}
|
||||
|
||||
if c.PodSyncWorkers == 0 {
|
||||
return errdefs.InvalidInput("pod sync workers must be greater than 0")
|
||||
}
|
||||
|
||||
var taint *corev1.Taint
|
||||
if !c.DisableTaint {
|
||||
var err error
|
||||
taint, err = getTaint(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
client, err := newClient(c.KubeConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a shared informer factory for Kubernetes pods in the current namespace (if specified) and scheduled to the current node.
|
||||
podInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(
|
||||
client,
|
||||
c.InformerResyncPeriod,
|
||||
kubeinformers.WithNamespace(c.KubeNamespace),
|
||||
kubeinformers.WithTweakListOptions(func(options *metav1.ListOptions) {
|
||||
options.FieldSelector = fields.OneTermEqualSelector("spec.nodeName", c.NodeName).String()
|
||||
}))
|
||||
podInformer := podInformerFactory.Core().V1().Pods()
|
||||
|
||||
// Create another shared informer factory for Kubernetes secrets and configmaps (not subject to any selectors).
|
||||
scmInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(client, c.InformerResyncPeriod)
|
||||
// Create a secret informer and a config map informer so we can pass their listers to the resource manager.
|
||||
secretInformer := scmInformerFactory.Core().V1().Secrets()
|
||||
configMapInformer := scmInformerFactory.Core().V1().ConfigMaps()
|
||||
serviceInformer := scmInformerFactory.Core().V1().Services()
|
||||
|
||||
go podInformerFactory.Start(ctx.Done())
|
||||
go scmInformerFactory.Start(ctx.Done())
|
||||
|
||||
rm, err := manager.NewResourceManager(podInformer.Lister(), secretInformer.Lister(), configMapInformer.Lister(), serviceInformer.Lister())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create resource manager")
|
||||
}
|
||||
|
||||
apiConfig, err := getAPIConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setupTracing(ctx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initConfig := providers.InitConfig{
|
||||
ConfigPath: c.ProviderConfigPath,
|
||||
NodeName: c.NodeName,
|
||||
OperatingSystem: c.OperatingSystem,
|
||||
ResourceManager: rm,
|
||||
DaemonPort: int32(c.ListenPort),
|
||||
InternalIP: os.Getenv("VKUBELET_POD_IP"),
|
||||
KubeClusterDomain: c.KubeClusterDomain,
|
||||
}
|
||||
|
||||
pInit := s.Get(c.Provider)
|
||||
if pInit == nil {
|
||||
return errors.Errorf("provider %q not found", c.Provider)
|
||||
}
|
||||
|
||||
p, err := pInit(initConfig)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error initializing provider %s", c.Provider)
|
||||
}
|
||||
|
||||
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{
|
||||
"provider": c.Provider,
|
||||
"operatingSystem": c.OperatingSystem,
|
||||
"node": c.NodeName,
|
||||
"watchedNamespace": c.KubeNamespace,
|
||||
}))
|
||||
|
||||
var leaseClient v1beta1.LeaseInterface
|
||||
if c.EnableNodeLease {
|
||||
leaseClient = client.CoordinationV1beta1().Leases(corev1.NamespaceNodeLease)
|
||||
}
|
||||
|
||||
pNode := NodeFromProvider(ctx, c.NodeName, taint, p, c.Version)
|
||||
nodeRunner, err := node.NewNodeController(
|
||||
node.NaiveNodeProvider{},
|
||||
pNode,
|
||||
client.CoreV1().Nodes(),
|
||||
node.WithNodeEnableLeaseV1Beta1(leaseClient, nil),
|
||||
node.WithNodeStatusUpdateErrorHandler(func(ctx context.Context, err error) error {
|
||||
if !k8serrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.G(ctx).Debug("node not found")
|
||||
newNode := pNode.DeepCopy()
|
||||
newNode.ResourceVersion = ""
|
||||
_, err = client.CoreV1().Nodes().Create(newNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.G(ctx).Debug("created new node")
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
log.G(ctx).Fatal(err)
|
||||
}
|
||||
|
||||
eb := record.NewBroadcaster()
|
||||
eb.StartLogging(log.G(ctx).Infof)
|
||||
eb.StartRecordingToSink(&corev1client.EventSinkImpl{Interface: client.CoreV1().Events(c.KubeNamespace)})
|
||||
|
||||
pc, err := node.NewPodController(node.PodControllerConfig{
|
||||
PodClient: client.CoreV1(),
|
||||
PodInformer: podInformer,
|
||||
EventRecorder: eb.NewRecorder(scheme.Scheme, corev1.EventSource{Component: path.Join(pNode.Name, "pod-controller")}),
|
||||
Provider: p,
|
||||
SecretInformer: secretInformer,
|
||||
ConfigMapInformer: configMapInformer,
|
||||
ServiceInformer: serviceInformer,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up pod controller")
|
||||
}
|
||||
|
||||
cancelHTTP, err := setupHTTPServer(ctx, p, apiConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancelHTTP()
|
||||
|
||||
go func() {
|
||||
if err := pc.Run(ctx, c.PodSyncWorkers); err != nil && errors.Cause(err) != context.Canceled {
|
||||
log.G(ctx).Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if c.StartupTimeout > 0 {
|
||||
// If there is a startup timeout, it does two things:
|
||||
// 1. It causes the VK to shutdown if we haven't gotten into an operational state in a time period
|
||||
// 2. It prevents node advertisement from happening until we're in an operational state
|
||||
err = waitFor(ctx, c.StartupTimeout, pc.Ready())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := nodeRunner.Run(ctx); err != nil {
|
||||
log.G(ctx).Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.G(ctx).Info("Initialized")
|
||||
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitFor(ctx context.Context, time time.Duration, ready <-chan struct{}) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, time)
|
||||
defer cancel()
|
||||
|
||||
// Wait for the VK / PC close the the ready channel, or time out and return
|
||||
log.G(ctx).Info("Waiting for pod controller / VK to be ready")
|
||||
|
||||
select {
|
||||
case <-ready:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return errors.Wrap(ctx.Err(), "Error while starting up VK")
|
||||
}
|
||||
}
|
||||
|
||||
func newClient(configPath string) (*kubernetes.Clientset, error) {
|
||||
var config *rest.Config
|
||||
|
||||
// Check if the kubeConfig file exists.
|
||||
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
|
||||
// Get the kubeconfig from the filepath.
|
||||
config, err = clientcmd.BuildConfigFromFlags("", configPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error building client config")
|
||||
}
|
||||
} else {
|
||||
// Set to in-cluster config.
|
||||
config, err = rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error building in cluster config")
|
||||
}
|
||||
}
|
||||
|
||||
if masterURI := os.Getenv("MASTER_URI"); masterURI != "" {
|
||||
config.Host = masterURI
|
||||
}
|
||||
|
||||
return kubernetes.NewForConfig(config)
|
||||
}
|
||||
@@ -19,12 +19,12 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/internal/provider"
|
||||
)
|
||||
|
||||
// NewCommand creates a new providers subcommand
|
||||
// This subcommand is used to determine which providers are registered.
|
||||
func NewCommand(s *providers.Store) *cobra.Command {
|
||||
func NewCommand(s *provider.Store) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "providers",
|
||||
Short: "Show the list of supported providers",
|
||||
@@ -41,13 +41,12 @@ func NewCommand(s *providers.Store) *cobra.Command {
|
||||
fmt.Fprintln(cmd.OutOrStderr(), "no such provider", args[0])
|
||||
|
||||
// TODO(@cpuuy83): would be nice to not short-circuit the exit here
|
||||
// But at the momemt this seems to be the only way to exit non-zero and
|
||||
// But at the moment this seems to be the only way to exit non-zero and
|
||||
// handle our own error output
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Fprintln(cmd.OutOrStdout(), args[0])
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/klog"
|
||||
klog "k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type mapVar map[string]string
|
||||
@@ -59,7 +59,13 @@ func (mv mapVar) Type() string {
|
||||
|
||||
func installFlags(flags *pflag.FlagSet, c *Opts) {
|
||||
flags.StringVar(&c.KubeConfigPath, "kubeconfig", c.KubeConfigPath, "kube config file to use for connecting to the Kubernetes API server")
|
||||
|
||||
flags.StringVar(&c.KubeNamespace, "namespace", c.KubeNamespace, "kubernetes namespace (default is 'all')")
|
||||
/* #nosec */
|
||||
flags.MarkDeprecated("namespace", "Nodes must watch for pods in all namespaces. This option is now ignored.") //nolint:errcheck
|
||||
/* #nosec */
|
||||
flags.MarkHidden("namespace") //nolint:errcheck
|
||||
|
||||
flags.StringVar(&c.KubeClusterDomain, "cluster-domain", c.KubeClusterDomain, "kubernetes cluster-domain (default is 'cluster.local')")
|
||||
flags.StringVar(&c.NodeName, "nodename", c.NodeName, "kubernetes node name")
|
||||
flags.StringVar(&c.OperatingSystem, "os", c.OperatingSystem, "Operating System (Linux/Windows)")
|
||||
@@ -68,11 +74,18 @@ func installFlags(flags *pflag.FlagSet, c *Opts) {
|
||||
flags.StringVar(&c.MetricsAddr, "metrics-addr", c.MetricsAddr, "address to listen for metrics/stats requests")
|
||||
|
||||
flags.StringVar(&c.TaintKey, "taint", c.TaintKey, "Set node taint key")
|
||||
|
||||
flags.BoolVar(&c.DisableTaint, "disable-taint", c.DisableTaint, "disable the virtual-kubelet node taint")
|
||||
flags.MarkDeprecated("taint", "Taint key should now be configured using the VK_TAINT_KEY environment variable")
|
||||
/* #nosec */
|
||||
flags.MarkDeprecated("taint", "Taint key should now be configured using the VK_TAINT_KEY environment variable") //nolint:errcheck
|
||||
|
||||
flags.IntVar(&c.PodSyncWorkers, "pod-sync-workers", c.PodSyncWorkers, `set the number of pod synchronization workers`)
|
||||
|
||||
flags.BoolVar(&c.EnableNodeLease, "enable-node-lease", c.EnableNodeLease, `use node leases (1.13) for node heartbeats`)
|
||||
/* #nosec */
|
||||
flags.MarkDeprecated("enable-node-lease", "leases are always enabled") //nolint:errcheck
|
||||
/* #nosec */
|
||||
flags.MarkHidden("enable-node-lease") //nolint:errcheck
|
||||
|
||||
flags.StringSliceVar(&c.TraceExporters, "trace-exporter", c.TraceExporters, fmt.Sprintf("sets the tracing exporter to use, available exporters: %s", AvailableTraceExporters()))
|
||||
flags.StringVar(&c.TraceConfig.ServiceName, "trace-service-name", c.TraceConfig.ServiceName, "sets the name of the service used to register with the trace exporter")
|
||||
@@ -81,6 +94,11 @@ func installFlags(flags *pflag.FlagSet, c *Opts) {
|
||||
|
||||
flags.DurationVar(&c.InformerResyncPeriod, "full-resync-period", c.InformerResyncPeriod, "how often to perform a full resync of pods between kubernetes and the provider")
|
||||
flags.DurationVar(&c.StartupTimeout, "startup-timeout", c.StartupTimeout, "How long to wait for the virtual-kubelet to start")
|
||||
flags.DurationVar(&c.StreamIdleTimeout, "stream-idle-timeout", c.StreamIdleTimeout,
|
||||
"stream-idle-timeout is the maximum time a streaming connection can be idle before the connection is"+
|
||||
" automatically closed, default 30s.")
|
||||
flags.DurationVar(&c.StreamCreationTimeout, "stream-creation-timeout", c.StreamCreationTimeout,
|
||||
"stream-creation-timeout is the maximum time for streaming connection, default 30s.")
|
||||
|
||||
flagset := flag.NewFlagSet("klog", flag.PanicOnError)
|
||||
klog.InitFlags(flagset)
|
||||
46
cmd/virtual-kubelet/internal/commands/root/http.go
Normal file
46
cmd/virtual-kubelet/internal/commands/root/http.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright © 2017 The virtual-kubelet authors
|
||||
//
|
||||
// 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 root
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type apiServerConfig struct {
|
||||
CertPath string
|
||||
KeyPath string
|
||||
CACertPath string
|
||||
Addr string
|
||||
MetricsAddr string
|
||||
StreamIdleTimeout time.Duration
|
||||
StreamCreationTimeout time.Duration
|
||||
}
|
||||
|
||||
func getAPIConfig(c Opts) (*apiServerConfig, error) {
|
||||
config := apiServerConfig{
|
||||
CertPath: os.Getenv("APISERVER_CERT_LOCATION"),
|
||||
KeyPath: os.Getenv("APISERVER_KEY_LOCATION"),
|
||||
CACertPath: os.Getenv("APISERVER_CA_CERT_LOCATION"),
|
||||
}
|
||||
|
||||
config.Addr = fmt.Sprintf(":%d", c.ListenPort)
|
||||
config.MetricsAddr = c.MetricsAddr
|
||||
config.StreamIdleTimeout = c.StreamIdleTimeout
|
||||
config.StreamCreationTimeout = c.StreamCreationTimeout
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
@@ -15,54 +15,10 @@
|
||||
package root
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/errdefs"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const osLabel = "beta.kubernetes.io/os"
|
||||
|
||||
// NodeFromProvider builds a kubernetes node object from a provider
|
||||
// This is a temporary solution until node stuff actually split off from the provider interface itself.
|
||||
func NodeFromProvider(ctx context.Context, name string, taint *v1.Taint, p providers.Provider, version string) *v1.Node {
|
||||
taints := make([]v1.Taint, 0)
|
||||
|
||||
if taint != nil {
|
||||
taints = append(taints, *taint)
|
||||
}
|
||||
|
||||
node := &v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: map[string]string{
|
||||
"type": "virtual-kubelet",
|
||||
"kubernetes.io/role": "agent",
|
||||
"kubernetes.io/hostname": name,
|
||||
},
|
||||
},
|
||||
Spec: v1.NodeSpec{
|
||||
Taints: taints,
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
NodeInfo: v1.NodeSystemInfo{
|
||||
Architecture: "amd64",
|
||||
KubeletVersion: version,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
p.ConfigureNode(ctx, node)
|
||||
if _, ok := node.ObjectMeta.Labels[osLabel]; !ok {
|
||||
node.ObjectMeta.Labels[osLabel] = strings.ToLower(node.Status.NodeInfo.OperatingSystem)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// getTaint creates a taint using the provided key/value.
|
||||
// Taint effect is read from the environment
|
||||
// The taint key/value may be overwritten by the environment.
|
||||
@@ -80,7 +36,7 @@ func getTaint(c Opts) (*corev1.Taint, error) {
|
||||
|
||||
key = getEnv("VKUBELET_TAINT_KEY", key)
|
||||
value = getEnv("VKUBELET_TAINT_VALUE", value)
|
||||
effectEnv := getEnv("VKUBELET_TAINT_EFFECT", string(c.TaintEffect))
|
||||
effectEnv := getEnv("VKUBELET_TAINT_EFFECT", c.TaintEffect)
|
||||
|
||||
var effect corev1.TaintEffect
|
||||
switch effectEnv {
|
||||
@@ -15,6 +15,7 @@
|
||||
package root
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -28,7 +29,7 @@ import (
|
||||
// Defaults for root command options
|
||||
const (
|
||||
DefaultNodeName = "virtual-kubelet"
|
||||
DefaultOperatingSystem = "Linux"
|
||||
DefaultOperatingSystem = "linux"
|
||||
DefaultInformerResyncPeriod = 1 * time.Minute
|
||||
DefaultMetricsAddr = ":10255"
|
||||
DefaultListenPort = 10250 // TODO(cpuguy83)(VK1.0): Change this to an addr instead of just a port.. we should not be listening on all interfaces.
|
||||
@@ -36,8 +37,10 @@ const (
|
||||
DefaultKubeNamespace = corev1.NamespaceAll
|
||||
DefaultKubeClusterDomain = "cluster.local"
|
||||
|
||||
DefaultTaintEffect = string(corev1.TaintEffectNoSchedule)
|
||||
DefaultTaintKey = "virtual-kubelet.io/provider"
|
||||
DefaultTaintEffect = string(corev1.TaintEffectNoSchedule)
|
||||
DefaultTaintKey = "virtual-kubelet.io/provider"
|
||||
DefaultStreamIdleTimeout = 30 * time.Second
|
||||
DefaultStreamCreationTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
// Opts stores all the options for configuring the root virtual-kubelet command.
|
||||
@@ -84,10 +87,17 @@ type Opts struct {
|
||||
|
||||
// Startup Timeout is how long to wait for the kubelet to start
|
||||
StartupTimeout time.Duration
|
||||
// StreamIdleTimeout is the maximum time a streaming connection
|
||||
// can be idle before the connection is automatically closed.
|
||||
StreamIdleTimeout time.Duration
|
||||
// StreamCreationTimeout is the maximum time for streaming connection
|
||||
StreamCreationTimeout time.Duration
|
||||
|
||||
Version string
|
||||
}
|
||||
|
||||
const maxInt32 = 1<<31 - 1
|
||||
|
||||
// SetDefaultOpts sets default options for unset values on the passed in option struct.
|
||||
// Fields tht are already set will not be modified.
|
||||
func SetDefaultOpts(c *Opts) error {
|
||||
@@ -121,6 +131,10 @@ func SetDefaultOpts(c *Opts) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing KUBELET_PORT environment variable")
|
||||
}
|
||||
if p > maxInt32 {
|
||||
return fmt.Errorf("KUBELET_PORT environment variable is too large")
|
||||
}
|
||||
/* #nosec */
|
||||
c.ListenPort = int32(p)
|
||||
} else {
|
||||
c.ListenPort = DefaultListenPort
|
||||
@@ -152,5 +166,13 @@ func SetDefaultOpts(c *Opts) error {
|
||||
}
|
||||
}
|
||||
|
||||
if c.StreamIdleTimeout == 0 {
|
||||
c.StreamIdleTimeout = DefaultStreamIdleTimeout
|
||||
}
|
||||
|
||||
if c.StreamCreationTimeout == 0 {
|
||||
c.StreamCreationTimeout = DefaultStreamCreationTimeout
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
202
cmd/virtual-kubelet/internal/commands/root/root.go
Normal file
202
cmd/virtual-kubelet/internal/commands/root/root.go
Normal file
@@ -0,0 +1,202 @@
|
||||
// Copyright © 2017 The virtual-kubelet authors
|
||||
//
|
||||
// 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 root
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/internal/provider"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/errdefs"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/internal/manager"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/node"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/node/api"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/node/nodeutil"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
||||
)
|
||||
|
||||
// NewCommand creates a new top-level command.
|
||||
// This command is used to start the virtual-kubelet daemon
|
||||
func NewCommand(ctx context.Context, name string, s *provider.Store, c Opts) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: name,
|
||||
Short: name + " provides a virtual kubelet interface for your kubernetes cluster.",
|
||||
Long: name + ` implements the Kubelet interface with a pluggable
|
||||
backend implementation allowing users to create kubernetes nodes without running the kubelet.
|
||||
This allows users to schedule kubernetes workloads on nodes that aren't running Kubernetes.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runRootCommand(ctx, s, c)
|
||||
},
|
||||
}
|
||||
|
||||
installFlags(cmd.Flags(), &c)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRootCommand(ctx context.Context, s *provider.Store, c Opts) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
if ok := provider.ValidOperatingSystems[c.OperatingSystem]; !ok {
|
||||
return errdefs.InvalidInputf("operating system %q is not supported", c.OperatingSystem)
|
||||
}
|
||||
|
||||
if c.PodSyncWorkers == 0 {
|
||||
return errdefs.InvalidInput("pod sync workers must be greater than 0")
|
||||
}
|
||||
|
||||
var taint *corev1.Taint
|
||||
if !c.DisableTaint {
|
||||
var err error
|
||||
taint, err = getTaint(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
newProvider := func(cfg nodeutil.ProviderConfig) (nodeutil.Provider, node.NodeProvider, error) {
|
||||
rm, err := manager.NewResourceManager(cfg.Pods, cfg.Secrets, cfg.ConfigMaps, cfg.Services)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not create resource manager")
|
||||
}
|
||||
initConfig := provider.InitConfig{
|
||||
ConfigPath: c.ProviderConfigPath,
|
||||
NodeName: c.NodeName,
|
||||
OperatingSystem: c.OperatingSystem,
|
||||
ResourceManager: rm,
|
||||
DaemonPort: c.ListenPort,
|
||||
InternalIP: os.Getenv("VKUBELET_POD_IP"),
|
||||
KubeClusterDomain: c.KubeClusterDomain,
|
||||
}
|
||||
pInit := s.Get(c.Provider)
|
||||
if pInit == nil {
|
||||
return nil, nil, errors.Errorf("provider %q not found", c.Provider)
|
||||
}
|
||||
|
||||
p, err := pInit(initConfig)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "error initializing provider %s", c.Provider)
|
||||
}
|
||||
p.ConfigureNode(ctx, cfg.Node)
|
||||
cfg.Node.Status.NodeInfo.KubeletVersion = c.Version
|
||||
return p, nil, nil
|
||||
}
|
||||
|
||||
apiConfig, err := getAPIConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cm, err := nodeutil.NewNode(c.NodeName, newProvider, func(cfg *nodeutil.NodeConfig) error {
|
||||
cfg.KubeconfigPath = c.KubeConfigPath
|
||||
cfg.Handler = mux
|
||||
cfg.InformerResyncPeriod = c.InformerResyncPeriod
|
||||
|
||||
if taint != nil {
|
||||
cfg.NodeSpec.Spec.Taints = append(cfg.NodeSpec.Spec.Taints, *taint)
|
||||
}
|
||||
cfg.NodeSpec.Status.NodeInfo.Architecture = runtime.GOARCH
|
||||
cfg.NodeSpec.Status.NodeInfo.OperatingSystem = c.OperatingSystem
|
||||
|
||||
cfg.HTTPListenAddr = apiConfig.Addr
|
||||
cfg.StreamCreationTimeout = apiConfig.StreamCreationTimeout
|
||||
cfg.StreamIdleTimeout = apiConfig.StreamIdleTimeout
|
||||
cfg.DebugHTTP = true
|
||||
|
||||
cfg.NumWorkers = c.PodSyncWorkers
|
||||
|
||||
return nil
|
||||
},
|
||||
setAuth(c.NodeName, apiConfig),
|
||||
nodeutil.WithTLSConfig(
|
||||
nodeutil.WithKeyPairFromPath(apiConfig.CertPath, apiConfig.KeyPath),
|
||||
maybeCA(apiConfig.CACertPath),
|
||||
),
|
||||
nodeutil.AttachProviderRoutes(mux),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setupTracing(ctx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{
|
||||
"provider": c.Provider,
|
||||
"operatingSystem": c.OperatingSystem,
|
||||
"node": c.NodeName,
|
||||
"watchedNamespace": c.KubeNamespace,
|
||||
}))
|
||||
|
||||
go cm.Run(ctx) //nolint:errcheck
|
||||
|
||||
defer func() {
|
||||
log.G(ctx).Debug("Waiting for controllers to be done")
|
||||
cancel()
|
||||
<-cm.Done()
|
||||
}()
|
||||
|
||||
log.G(ctx).Info("Waiting for controller to be ready")
|
||||
if err := cm.WaitReady(ctx, c.StartupTimeout); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.G(ctx).Info("Ready")
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-cm.Done():
|
||||
return cm.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setAuth(node string, apiCfg *apiServerConfig) nodeutil.NodeOpt {
|
||||
if apiCfg.CACertPath == "" {
|
||||
return func(cfg *nodeutil.NodeConfig) error {
|
||||
cfg.Handler = api.InstrumentHandler(nodeutil.WithAuth(nodeutil.NoAuth(), cfg.Handler))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return func(cfg *nodeutil.NodeConfig) error {
|
||||
auth, err := nodeutil.WebhookAuth(cfg.Client, node, func(cfg *nodeutil.WebhookAuthConfig) error {
|
||||
var err error
|
||||
cfg.AuthnConfig.ClientCertificateCAContentProvider, err = dynamiccertificates.NewDynamicCAContentFromFile("ca-cert-bundle", apiCfg.CACertPath)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.Handler = api.InstrumentHandler(nodeutil.WithAuth(auth, cfg.Handler))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func maybeCA(p string) func(*tls.Config) error {
|
||||
if p == "" {
|
||||
return func(*tls.Config) error { return nil }
|
||||
}
|
||||
return nodeutil.WithCAFromPath(p)
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/errdefs"
|
||||
@@ -105,7 +106,8 @@ func setupZpages(ctx context.Context) {
|
||||
zpages.Handle(mux, "/debug")
|
||||
go func() {
|
||||
// This should never terminate, if it does, it will always terminate with an error
|
||||
e := http.Serve(listener, mux)
|
||||
srv := &http.Server{Handler: mux, ReadHeaderTimeout: 30 * time.Second}
|
||||
e := srv.Serve(listener)
|
||||
if e == http.ErrServerClosed {
|
||||
return
|
||||
}
|
||||
@@ -19,7 +19,8 @@ import (
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
type TracingExporterOptions struct {
|
||||
// TracingExporterOptions is the options passed to the tracing exporter init function.
|
||||
type TracingExporterOptions struct { //nolint: golint
|
||||
Tags map[string]string
|
||||
ServiceName string
|
||||
}
|
||||
@@ -29,7 +30,7 @@ var (
|
||||
)
|
||||
|
||||
// TracingExporterInitFunc is the function that is called to initialize an exporter.
|
||||
// This is used when registering an exporter and called when a user specifed they want to use the exporter.
|
||||
// This is used when registering an exporter and called when a user specified they want to use the exporter.
|
||||
type TracingExporterInitFunc func(TracingExporterOptions) (trace.Exporter, error)
|
||||
|
||||
// RegisterTracingExporter registers a tracing exporter.
|
||||
@@ -39,7 +40,7 @@ func RegisterTracingExporter(name string, f TracingExporterInitFunc) {
|
||||
}
|
||||
|
||||
// GetTracingExporter gets the specified tracing exporter passing in the options to the exporter init function.
|
||||
// For an exporter to be availbale here it must be registered with `RegisterTracingExporter`.
|
||||
// For an exporter to be available here it must be registered with `RegisterTracingExporter`.
|
||||
func GetTracingExporter(name string, opts TracingExporterOptions) (trace.Exporter, error) {
|
||||
f, ok := tracingExporters[name]
|
||||
if !ok {
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !no_jaeger_exporter
|
||||
// +build !no_jaeger_exporter
|
||||
|
||||
package root
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"go.opencensus.io/exporter/jaeger"
|
||||
"contrib.go.opencensus.io/exporter/jaeger"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
@@ -31,17 +32,17 @@ func init() {
|
||||
// NewJaegerExporter creates a new opencensus tracing exporter.
|
||||
func NewJaegerExporter(opts TracingExporterOptions) (trace.Exporter, error) {
|
||||
jOpts := jaeger.Options{
|
||||
Endpoint: os.Getenv("JAEGER_ENDPOINT"),
|
||||
AgentEndpoint: os.Getenv("JAEGER_AGENT_ENDPOINT"),
|
||||
Username: os.Getenv("JAEGER_USER"),
|
||||
Password: os.Getenv("JAEGER_PASSWORD"),
|
||||
CollectorEndpoint: os.Getenv("JAEGER_COLLECTOR_ENDPOINT"),
|
||||
AgentEndpoint: os.Getenv("JAEGER_AGENT_ENDPOINT"),
|
||||
Username: os.Getenv("JAEGER_USER"),
|
||||
Password: os.Getenv("JAEGER_PASSWORD"),
|
||||
Process: jaeger.Process{
|
||||
ServiceName: opts.ServiceName,
|
||||
},
|
||||
}
|
||||
|
||||
if jOpts.Endpoint == "" && jOpts.AgentEndpoint == "" {
|
||||
return nil, errors.New("Must specify either JAEGER_ENDPOINT or JAEGER_AGENT_ENDPOINT")
|
||||
if jOpts.CollectorEndpoint == "" && jOpts.AgentEndpoint == "" { // nolintlint:staticcheck
|
||||
return nil, errors.New("must specify either JAEGER_COLLECTOR_ENDPOINT or JAEGER_AGENT_ENDPOINT")
|
||||
}
|
||||
|
||||
for k, v := range opts.Tags {
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !no_ocagent_exporter
|
||||
// +build !no_ocagent_exporter
|
||||
|
||||
package root
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
stats "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
stats "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -41,8 +41,8 @@ var (
|
||||
)
|
||||
*/
|
||||
|
||||
// MockV0Provider implements the virtual-kubelet provider interface and stores pods in memory.
|
||||
type MockV0Provider struct {
|
||||
// MockProvider implements the virtual-kubelet provider interface and stores pods in memory.
|
||||
type MockProvider struct { //nolint:golint
|
||||
nodeName string
|
||||
operatingSystem string
|
||||
internalIP string
|
||||
@@ -53,21 +53,16 @@ type MockV0Provider struct {
|
||||
notifier func(*v1.Pod)
|
||||
}
|
||||
|
||||
// MockProvider is like MockV0Provider, but implements the PodNotifier interface
|
||||
type MockProvider struct {
|
||||
*MockV0Provider
|
||||
}
|
||||
|
||||
// MockConfig contains a mock virtual-kubelet's configurable parameters.
|
||||
type MockConfig struct {
|
||||
type MockConfig struct { //nolint:golint
|
||||
CPU string `json:"cpu,omitempty"`
|
||||
Memory string `json:"memory,omitempty"`
|
||||
Pods string `json:"pods,omitempty"`
|
||||
}
|
||||
|
||||
// NewMockProviderMockConfig creates a new MockV0Provider. Mock legacy provider does not implement the new asynchronous podnotifier interface
|
||||
func NewMockV0ProviderMockConfig(config MockConfig, nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*MockV0Provider, error) {
|
||||
//set defaults
|
||||
func NewMockProviderMockConfig(config MockConfig, nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*MockProvider, error) {
|
||||
// set defaults
|
||||
if config.CPU == "" {
|
||||
config.CPU = defaultCPUCapacity
|
||||
}
|
||||
@@ -77,7 +72,7 @@ func NewMockV0ProviderMockConfig(config MockConfig, nodeName, operatingSystem st
|
||||
if config.Pods == "" {
|
||||
config.Pods = defaultPodCapacity
|
||||
}
|
||||
provider := MockV0Provider{
|
||||
provider := MockProvider{
|
||||
nodeName: nodeName,
|
||||
operatingSystem: operatingSystem,
|
||||
internalIP: internalIP,
|
||||
@@ -85,32 +80,11 @@ func NewMockV0ProviderMockConfig(config MockConfig, nodeName, operatingSystem st
|
||||
pods: make(map[string]*v1.Pod),
|
||||
config: config,
|
||||
startTime: time.Now(),
|
||||
// By default notifier is set to a function which is a no-op. In the event we've implemented the PodNotifier interface,
|
||||
// it will be set, and then we'll call a real underlying implementation.
|
||||
// This makes it easier in the sense we don't need to wrap each method.
|
||||
notifier: func(*v1.Pod) {},
|
||||
}
|
||||
|
||||
return &provider, nil
|
||||
}
|
||||
|
||||
// NewMockV0Provider creates a new MockV0Provider
|
||||
func NewMockV0Provider(providerConfig, nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*MockV0Provider, error) {
|
||||
config, err := loadConfig(providerConfig, nodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewMockV0ProviderMockConfig(config, nodeName, operatingSystem, internalIP, daemonEndpointPort)
|
||||
}
|
||||
|
||||
// NewMockProviderMockConfig creates a new MockProvider with the given config
|
||||
func NewMockProviderMockConfig(config MockConfig, nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*MockProvider, error) {
|
||||
p, err := NewMockV0ProviderMockConfig(config, nodeName, operatingSystem, internalIP, daemonEndpointPort)
|
||||
|
||||
return &MockProvider{MockV0Provider: p}, err
|
||||
}
|
||||
|
||||
// NewMockProvider creates a new MockProvider, which implements the PodNotifier interface
|
||||
func NewMockProvider(providerConfig, nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*MockProvider, error) {
|
||||
config, err := loadConfig(providerConfig, nodeName)
|
||||
@@ -123,7 +97,7 @@ func NewMockProvider(providerConfig, nodeName, operatingSystem string, internalI
|
||||
|
||||
// loadConfig loads the given json configuration files.
|
||||
func loadConfig(providerConfig, nodeName string) (config MockConfig, err error) {
|
||||
data, err := ioutil.ReadFile(providerConfig)
|
||||
data, err := os.ReadFile(providerConfig)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
@@ -158,7 +132,7 @@ func loadConfig(providerConfig, nodeName string) (config MockConfig, err error)
|
||||
}
|
||||
|
||||
// CreatePod accepts a Pod definition and stores it in memory.
|
||||
func (p *MockV0Provider) CreatePod(ctx context.Context, pod *v1.Pod) error {
|
||||
func (p *MockProvider) CreatePod(ctx context.Context, pod *v1.Pod) error {
|
||||
ctx, span := trace.StartSpan(ctx, "CreatePod")
|
||||
defer span.End()
|
||||
|
||||
@@ -215,7 +189,7 @@ func (p *MockV0Provider) CreatePod(ctx context.Context, pod *v1.Pod) error {
|
||||
}
|
||||
|
||||
// UpdatePod accepts a Pod definition and updates its reference.
|
||||
func (p *MockV0Provider) UpdatePod(ctx context.Context, pod *v1.Pod) error {
|
||||
func (p *MockProvider) UpdatePod(ctx context.Context, pod *v1.Pod) error {
|
||||
ctx, span := trace.StartSpan(ctx, "UpdatePod")
|
||||
defer span.End()
|
||||
|
||||
@@ -236,7 +210,7 @@ func (p *MockV0Provider) UpdatePod(ctx context.Context, pod *v1.Pod) error {
|
||||
}
|
||||
|
||||
// DeletePod deletes the specified pod out of memory.
|
||||
func (p *MockV0Provider) DeletePod(ctx context.Context, pod *v1.Pod) (err error) {
|
||||
func (p *MockProvider) DeletePod(ctx context.Context, pod *v1.Pod) (err error) {
|
||||
ctx, span := trace.StartSpan(ctx, "DeletePod")
|
||||
defer span.End()
|
||||
|
||||
@@ -277,7 +251,7 @@ func (p *MockV0Provider) DeletePod(ctx context.Context, pod *v1.Pod) (err error)
|
||||
}
|
||||
|
||||
// GetPod returns a pod by name that is stored in memory.
|
||||
func (p *MockV0Provider) GetPod(ctx context.Context, namespace, name string) (pod *v1.Pod, err error) {
|
||||
func (p *MockProvider) GetPod(ctx context.Context, namespace, name string) (pod *v1.Pod, err error) {
|
||||
ctx, span := trace.StartSpan(ctx, "GetPod")
|
||||
defer func() {
|
||||
span.SetStatus(err)
|
||||
@@ -301,27 +275,27 @@ func (p *MockV0Provider) GetPod(ctx context.Context, namespace, name string) (po
|
||||
}
|
||||
|
||||
// GetContainerLogs retrieves the logs of a container by name from the provider.
|
||||
func (p *MockV0Provider) GetContainerLogs(ctx context.Context, namespace, podName, containerName string, opts api.ContainerLogOpts) (io.ReadCloser, error) {
|
||||
func (p *MockProvider) GetContainerLogs(ctx context.Context, namespace, podName, containerName string, opts api.ContainerLogOpts) (io.ReadCloser, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "GetContainerLogs")
|
||||
defer span.End()
|
||||
|
||||
// Add pod and container attributes to the current span.
|
||||
ctx = addAttributes(ctx, span, namespaceKey, namespace, nameKey, podName, containerNameKey, containerName)
|
||||
|
||||
log.G(ctx).Info("receive GetContainerLogs %q", podName)
|
||||
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||
log.G(ctx).Infof("receive GetContainerLogs %q", podName)
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
}
|
||||
|
||||
// RunInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
func (p *MockV0Provider) RunInContainer(ctx context.Context, namespace, name, container string, cmd []string, attach api.AttachIO) error {
|
||||
func (p *MockProvider) RunInContainer(ctx context.Context, namespace, name, container string, cmd []string, attach api.AttachIO) error {
|
||||
log.G(context.TODO()).Infof("receive ExecInContainer %q", container)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodStatus returns the status of a pod by name that is "running".
|
||||
// returns nil if a pod by that name is not found.
|
||||
func (p *MockV0Provider) GetPodStatus(ctx context.Context, namespace, name string) (*v1.PodStatus, error) {
|
||||
func (p *MockProvider) GetPodStatus(ctx context.Context, namespace, name string) (*v1.PodStatus, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "GetPodStatus")
|
||||
defer span.End()
|
||||
|
||||
@@ -339,7 +313,7 @@ func (p *MockV0Provider) GetPodStatus(ctx context.Context, namespace, name strin
|
||||
}
|
||||
|
||||
// GetPods returns a list of all pods known to be "running".
|
||||
func (p *MockV0Provider) GetPods(ctx context.Context) ([]*v1.Pod, error) {
|
||||
func (p *MockProvider) GetPods(ctx context.Context) ([]*v1.Pod, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "GetPods")
|
||||
defer span.End()
|
||||
|
||||
@@ -354,8 +328,8 @@ func (p *MockV0Provider) GetPods(ctx context.Context) ([]*v1.Pod, error) {
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
func (p *MockV0Provider) ConfigureNode(ctx context.Context, n *v1.Node) {
|
||||
ctx, span := trace.StartSpan(ctx, "mock.ConfigureNode")
|
||||
func (p *MockProvider) ConfigureNode(ctx context.Context, n *v1.Node) { //nolint:golint
|
||||
ctx, span := trace.StartSpan(ctx, "mock.ConfigureNode") //nolint:staticcheck,ineffassign
|
||||
defer span.End()
|
||||
|
||||
n.Status.Capacity = p.capacity()
|
||||
@@ -365,15 +339,16 @@ func (p *MockV0Provider) ConfigureNode(ctx context.Context, n *v1.Node) {
|
||||
n.Status.DaemonEndpoints = p.nodeDaemonEndpoints()
|
||||
os := p.operatingSystem
|
||||
if os == "" {
|
||||
os = "Linux"
|
||||
os = "linux"
|
||||
}
|
||||
n.Status.NodeInfo.OperatingSystem = os
|
||||
n.Status.NodeInfo.Architecture = "amd64"
|
||||
n.ObjectMeta.Labels["alpha.service-controller.kubernetes.io/exclude-balancer"] = "true"
|
||||
n.ObjectMeta.Labels["node.kubernetes.io/exclude-from-external-load-balancers"] = "true"
|
||||
}
|
||||
|
||||
// Capacity returns a resource list containing the capacity limits.
|
||||
func (p *MockV0Provider) capacity() v1.ResourceList {
|
||||
func (p *MockProvider) capacity() v1.ResourceList {
|
||||
return v1.ResourceList{
|
||||
"cpu": resource.MustParse(p.config.CPU),
|
||||
"memory": resource.MustParse(p.config.Memory),
|
||||
@@ -383,16 +358,16 @@ func (p *MockV0Provider) capacity() v1.ResourceList {
|
||||
|
||||
// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), for updates to the node status
|
||||
// within Kubernetes.
|
||||
func (p *MockV0Provider) nodeConditions() []v1.NodeCondition {
|
||||
func (p *MockProvider) nodeConditions() []v1.NodeCondition {
|
||||
// TODO: Make this configurable
|
||||
return []v1.NodeCondition{
|
||||
{
|
||||
Type: "Ready",
|
||||
Status: v1.ConditionTrue,
|
||||
Status: v1.ConditionFalse,
|
||||
LastHeartbeatTime: metav1.Now(),
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: "KubeletReady",
|
||||
Message: "kubelet is ready.",
|
||||
Reason: "KubeletPending",
|
||||
Message: "kubelet is pending.",
|
||||
},
|
||||
{
|
||||
Type: "OutOfDisk",
|
||||
@@ -432,7 +407,7 @@ func (p *MockV0Provider) nodeConditions() []v1.NodeCondition {
|
||||
|
||||
// NodeAddresses returns a list of addresses for the node status
|
||||
// within Kubernetes.
|
||||
func (p *MockV0Provider) nodeAddresses() []v1.NodeAddress {
|
||||
func (p *MockProvider) nodeAddresses() []v1.NodeAddress {
|
||||
return []v1.NodeAddress{
|
||||
{
|
||||
Type: "InternalIP",
|
||||
@@ -443,7 +418,7 @@ func (p *MockV0Provider) nodeAddresses() []v1.NodeAddress {
|
||||
|
||||
// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status
|
||||
// within Kubernetes.
|
||||
func (p *MockV0Provider) nodeDaemonEndpoints() v1.NodeDaemonEndpoints {
|
||||
func (p *MockProvider) nodeDaemonEndpoints() v1.NodeDaemonEndpoints {
|
||||
return v1.NodeDaemonEndpoints{
|
||||
KubeletEndpoint: v1.DaemonEndpoint{
|
||||
Port: p.daemonEndpointPort,
|
||||
@@ -452,8 +427,9 @@ func (p *MockV0Provider) nodeDaemonEndpoints() v1.NodeDaemonEndpoints {
|
||||
}
|
||||
|
||||
// GetStatsSummary returns dummy stats for all pods known by this provider.
|
||||
func (p *MockV0Provider) GetStatsSummary(ctx context.Context) (*stats.Summary, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "GetStatsSummary")
|
||||
func (p *MockProvider) GetStatsSummary(ctx context.Context) (*stats.Summary, error) {
|
||||
var span trace.Span
|
||||
ctx, span = trace.StartSpan(ctx, "GetStatsSummary") //nolint: ineffassign,staticcheck
|
||||
defer span.End()
|
||||
|
||||
// Grab the current timestamp so we can report it as the time the stats were generated.
|
||||
@@ -491,10 +467,14 @@ func (p *MockV0Provider) GetStatsSummary(ctx context.Context) (*stats.Summary, e
|
||||
for _, container := range pod.Spec.Containers {
|
||||
// Grab a dummy value to be used as the total CPU usage.
|
||||
// The value should fit a uint32 in order to avoid overflows later on when computing pod stats.
|
||||
|
||||
/* #nosec */
|
||||
dummyUsageNanoCores := uint64(rand.Uint32())
|
||||
totalUsageNanoCores += dummyUsageNanoCores
|
||||
// Create a dummy value to be used as the total RAM usage.
|
||||
// The value should fit a uint32 in order to avoid overflows later on when computing pod stats.
|
||||
|
||||
/* #nosec */
|
||||
dummyUsageBytes := uint64(rand.Uint32())
|
||||
totalUsageBytes += dummyUsageBytes
|
||||
// Append a ContainerStats object containing the dummy stats to the PodStats object.
|
||||
16
cmd/virtual-kubelet/internal/provider/provider.go
Normal file
16
cmd/virtual-kubelet/internal/provider/provider.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/node/nodeutil"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Provider wraps the core provider type with an extra function needed to bootstrap the node
|
||||
type Provider interface {
|
||||
nodeutil.Provider
|
||||
// ConfigureNode enables a provider to configure the node object that
|
||||
// will be used for Kubernetes.
|
||||
ConfigureNode(context.Context, *v1.Node)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package providers
|
||||
package provider
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/errdefs"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/internal/manager"
|
||||
)
|
||||
|
||||
// Store is used for registering/fetching providers
|
||||
@@ -13,7 +13,7 @@ type Store struct {
|
||||
ls map[string]InitFunc
|
||||
}
|
||||
|
||||
func NewStore() *Store {
|
||||
func NewStore() *Store { //nolint:golint
|
||||
return &Store{
|
||||
ls: make(map[string]InitFunc),
|
||||
}
|
||||
@@ -71,4 +71,4 @@ type InitConfig struct {
|
||||
ResourceManager *manager.ResourceManager
|
||||
}
|
||||
|
||||
type InitFunc func(InitConfig) (Provider, error)
|
||||
type InitFunc func(InitConfig) (Provider, error) //nolint:golint
|
||||
@@ -1,13 +1,13 @@
|
||||
package providers
|
||||
package provider
|
||||
|
||||
const (
|
||||
// OperatingSystemLinux is the configuration value for defining Linux.
|
||||
OperatingSystemLinux = "Linux"
|
||||
OperatingSystemLinux = "linux"
|
||||
// OperatingSystemWindows is the configuration value for defining Windows.
|
||||
OperatingSystemWindows = "Windows"
|
||||
OperatingSystemWindows = "windows"
|
||||
)
|
||||
|
||||
type OperatingSystems map[string]bool
|
||||
type OperatingSystems map[string]bool //nolint:golint
|
||||
|
||||
var (
|
||||
// ValidOperatingSystems defines the group of operating systems
|
||||
@@ -18,7 +18,7 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func (o OperatingSystems) Names() []string {
|
||||
func (o OperatingSystems) Names() []string { //nolint:golint
|
||||
keys := make([]string, 0, len(o))
|
||||
for k := range o {
|
||||
keys = append(keys, k)
|
||||
@@ -25,12 +25,12 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
cmdproviders "github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/commands/providers"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/commands/root"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/commands/version"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/internal/commands/providers"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/internal/commands/root"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/internal/commands/version"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/internal/provider"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
logruslogger "github.com/virtual-kubelet/virtual-kubelet/log/logrus"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/trace"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/trace/opencensus"
|
||||
)
|
||||
@@ -38,7 +38,7 @@ import (
|
||||
var (
|
||||
buildVersion = "N/A"
|
||||
buildTime = "N/A"
|
||||
k8sVersion = "v1.13.7" // This should follow the version of k8s.io/kubernetes we are importing
|
||||
k8sVersion = "v1.15.2" // This should follow the version of k8s.io/kubernetes we are importing
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -57,11 +57,11 @@ func main() {
|
||||
optsErr := root.SetDefaultOpts(&opts)
|
||||
opts.Version = strings.Join([]string{k8sVersion, "vk", buildVersion}, "-")
|
||||
|
||||
s := providers.NewStore()
|
||||
s := provider.NewStore()
|
||||
registerMock(s)
|
||||
|
||||
rootCmd := root.NewCommand(ctx, filepath.Base(os.Args[0]), s, opts)
|
||||
rootCmd.AddCommand(version.NewCommand(buildVersion, buildTime), cmdproviders.NewCommand(s))
|
||||
rootCmd.AddCommand(version.NewCommand(buildVersion, buildTime), providers.NewCommand(s))
|
||||
preRun := rootCmd.PreRunE
|
||||
|
||||
var logLevel string
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/mock"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/internal/provider"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/internal/provider/mock"
|
||||
)
|
||||
|
||||
func registerMock(s *providers.Store) {
|
||||
s.Register("mock", func(cfg providers.InitConfig) (providers.Provider, error) {
|
||||
return mock.NewMockProvider(
|
||||
cfg.ConfigPath,
|
||||
cfg.NodeName,
|
||||
cfg.OperatingSystem,
|
||||
cfg.InternalIP,
|
||||
cfg.DaemonPort,
|
||||
)
|
||||
})
|
||||
|
||||
s.Register("mockV0", func(cfg providers.InitConfig) (providers.Provider, error) {
|
||||
func registerMock(s *provider.Store) {
|
||||
/* #nosec */
|
||||
s.Register("mock", func(cfg provider.InitConfig) (provider.Provider, error) { //nolint:errcheck
|
||||
return mock.NewMockProvider(
|
||||
cfg.ConfigPath,
|
||||
cfg.NodeName,
|
||||
|
||||
24
doc.go
Normal file
24
doc.go
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Package virtualkubelet is currently just for providing docs for godoc.
|
||||
|
||||
Virtual Kubelet is a project which aims to provide a library that can be
|
||||
consumed by other projects to build a Kubernetes node agent that performs the
|
||||
same basic role as the Kubelet, but fully customize the behavior.
|
||||
|
||||
*Note*: Virtual Kubelet is not the Kubelet.
|
||||
|
||||
All of the business logic for virtual-kubelet is in the `node` package. The
|
||||
node package has controllers for managing the node in Kubernetes and running
|
||||
scheduled pods against a backend service. The backend service along with the
|
||||
code wrapping what is provided in the node package is what consumers of this
|
||||
project would implement. In the interest of not duplicating examples, please
|
||||
see that package on how to get started using virtual kubelet.
|
||||
|
||||
Virtual Kubelet supports propagation of logging and traces through a context.
|
||||
See the "log" and "trace" packages for how to use this.
|
||||
|
||||
Errors produced by and consumed from the node package are expected to conform to
|
||||
error types defined in the "errdefs" package in order to be able to understand
|
||||
the kind of failure that occurred and react accordingly.
|
||||
*/
|
||||
package virtualkubelet
|
||||
4
errdefs/doc.go
Normal file
4
errdefs/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package errdefs defines the error types that are understood by other packages
|
||||
// in this project. Consumers of this project should look here to know how to
|
||||
// produce and consume errors for this project.
|
||||
package errdefs
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// InvalidInput is an error interface which denotes whether the opration failed due
|
||||
// ErrInvalidInput is an error interface which denotes whether the opration failed due
|
||||
// to a the resource not being found.
|
||||
type ErrInvalidInput interface {
|
||||
InvalidInput() bool
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// NotFound is an error interface which denotes whether the opration failed due
|
||||
// ErrNotFound is an error interface which denotes whether the opration failed due
|
||||
// to a the resource not being found.
|
||||
type ErrNotFound interface {
|
||||
NotFound() bool
|
||||
|
||||
147
go.mod
147
go.mod
@@ -1,61 +1,100 @@
|
||||
module github.com/virtual-kubelet/virtual-kubelet
|
||||
|
||||
go 1.12
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/docker/spdystream v0.0.0-20170912183627-bc6354cbbc29 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.2.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/go-cmp v0.2.0
|
||||
github.com/google/gofuzz v1.0.0 // indirect
|
||||
github.com/googleapis/gnostic v0.1.0 // indirect
|
||||
github.com/gorilla/mux v1.6.2
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.1 // indirect
|
||||
github.com/imdario/mergo v0.3.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.6 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
contrib.go.opencensus.io/exporter/jaeger v0.2.1
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.7.0
|
||||
github.com/bombsimon/logrusr/v3 v3.1.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
github.com/onsi/gomega v1.5.0 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/sirupsen/logrus v1.4.1
|
||||
github.com/spf13/cobra v0.0.2
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
go.opencensus.io v0.20.2
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c // indirect
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e // indirect
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db // indirect
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||
google.golang.org/api v0.3.2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 // indirect
|
||||
google.golang.org/grpc v1.20.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
gotest.tools v0.0.0-20181223230014-1083505acf35
|
||||
k8s.io/api v0.0.0-20190222213804-5cb15d344471
|
||||
k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628
|
||||
k8s.io/apiserver v0.0.0-20181213151703-3ccfe8365421 // indirect
|
||||
k8s.io/client-go v10.0.0+incompatible
|
||||
k8s.io/klog v0.1.0
|
||||
k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22 // indirect
|
||||
k8s.io/kubernetes v1.13.7
|
||||
k8s.io/utils v0.0.0-20180801164400-045dc31ee5c4 // indirect
|
||||
sigs.k8s.io/yaml v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
go.opencensus.io v0.24.0
|
||||
go.opentelemetry.io/otel v1.12.0
|
||||
go.opentelemetry.io/otel/sdk v1.12.0
|
||||
go.opentelemetry.io/otel/trace v1.12.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/time v0.3.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
k8s.io/api v0.26.1
|
||||
k8s.io/apimachinery v0.26.1
|
||||
k8s.io/apiserver v0.26.1
|
||||
k8s.io/client-go v0.26.1
|
||||
k8s.io/klog/v2 v2.90.0
|
||||
k8s.io/kubelet v0.26.1
|
||||
k8s.io/utils v0.0.0-20230202215443-34013725500c
|
||||
sigs.k8s.io/controller-runtime v0.14.4
|
||||
)
|
||||
|
||||
replace k8s.io/api => k8s.io/api v0.0.0-20190222213804-5cb15d344471
|
||||
|
||||
replace k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // 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.19.14 // indirect
|
||||
github.com/gogo/protobuf v1.3.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.5.7-v3refs // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/uuid v1.1.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // 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/prometheus/client_golang v1.14.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/uber/jaeger-client-go v2.25.0+incompatible // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.31.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/term v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
google.golang.org/api v0.43.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
|
||||
google.golang.org/grpc v1.49.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // 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.26.1 // indirect
|
||||
k8s.io/component-base v0.26.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35 // 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
|
||||
)
|
||||
|
||||
863
go.sum
863
go.sum
@@ -1,271 +1,786 @@
|
||||
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=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12 h1:jGFvw3l57ViIVEPKKEUXPcLYIXJmQxLUh6ey1eJhwyc=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
contrib.go.opencensus.io/exporter/jaeger v0.2.1 h1:yGBYzYMewVL0yO9qqJv3Z5+IRhPdU7e9o/2oKpX4YvI=
|
||||
contrib.go.opencensus.io/exporter/jaeger v0.2.1/go.mod h1:Y8IsLgdxqh1QxYxPC5IgXVmBaeLUeQFfBeBi9PbeZd0=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.7.0 h1:BEfdCTXfMV30tLZD8c9n64V/tIZX5+9sXiuFLnrr1k8=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.7.0/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
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/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/bombsimon/logrusr/v3 v3.1.0 h1:zORbLM943D+hDMGgyjMhSAz/iDz86ZV72qaak/CA0zQ=
|
||||
github.com/bombsimon/logrusr/v3 v3.1.0/go.mod h1:PksPPgSFEL2I52pla2glgCyyd2OqOHAnFF5E+g8Ixco=
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
|
||||
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||
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/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/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
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/docker/spdystream v0.0.0-20170912183627-bc6354cbbc29 h1:llBx5m8Gk0lrAaiLud2wktkX/e8haX7Ru0oVfQqtZQ4=
|
||||
github.com/docker/spdystream v0.0.0-20170912183627-bc6354cbbc29/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f h1:8GDPb0tCY8LQ+OJ3dbHb5sA6YZWXFORQYZx5sdsTlMs=
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f h1:AUj1VoZUfhPhOPHULCQQDnGhRelpFWHMLhQVWDsS0v4=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc=
|
||||
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
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/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
github.com/evanphx/json-patch v4.12.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/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
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-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
|
||||
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.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff h1:kOkM9whyQYodu09SJ6W3NCsHG7crFaJILQ22Gozp3lg=
|
||||
github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
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/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
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/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
|
||||
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI=
|
||||
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.4 h1:mKkfHkZWD8dC7WxKx3N9WCF0Y+dLau45704YQmY6H94=
|
||||
github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
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/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
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.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/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/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/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-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
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/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
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/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
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 h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc=
|
||||
github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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 v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
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.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
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.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
|
||||
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/spf13/cobra v0.0.2 h1:NfkwRbgViGoyjBKsLI0QMDcuMnhM+SBg3T0cGfpvKDE=
|
||||
github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
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/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2 h1:NAfh7zF0/3/HqtMvJNZ/RFrSlCE6ZTlHmKfhL/Dm1Jk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/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.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 h1:Ajldaqhxqw/gNzQA45IKFWLdG7jZuXX/wBW1d5qvbUI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c=
|
||||
go.opentelemetry.io/otel v1.12.0 h1:IgfC7kqQrRccIKuB7Cl+SRUmsKbEwSGPr0Eu+/ht1SQ=
|
||||
go.opentelemetry.io/otel v1.12.0/go.mod h1:geaoz0L0r1BEOR81k7/n9W4TCXYCJ7bPO7K374jQHG0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 h1:KtiUEhQmj/Pa874bVYKGNVdq8NPKiacPbaRRtgXi+t4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0=
|
||||
go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs=
|
||||
go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A=
|
||||
go.opentelemetry.io/otel/sdk v1.12.0 h1:8npliVYV7qc0t1FKdpU08eMnOjgPFMnriPhn0HH4q3o=
|
||||
go.opentelemetry.io/otel/sdk v1.12.0/go.mod h1:WYcvtgquYvgODEvxOry5owO2y9MyciW7JqMz6cpXShE=
|
||||
go.opentelemetry.io/otel/trace v1.12.0 h1:p28in++7Kd0r2d8gSt931O57fdjUyWxkVbESuILAeUc=
|
||||
go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
|
||||
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
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-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/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.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc=
|
||||
golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
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-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
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 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/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.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.3.2 h1:iTp+3yyl/KOtxa/d1/JUE0GGSoR6FuW5udver22iwpw=
|
||||
google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
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-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-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/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=
|
||||
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.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0 h1:4sAyIHT6ZohtAQDoxws+ez7bROYmUlOVvsUscYCDTqA=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
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-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo=
|
||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
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-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I=
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0 h1:DlsSIrgEBuZAUFJcta2B5i/lzeHHbnfkNFAfFXLVFYQ=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.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.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
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/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
|
||||
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gotest.tools v0.0.0-20181223230014-1083505acf35 h1:zpdCK+REwbk+rqjJmHhiCN6iBIigrZ39glqSF0P3KF0=
|
||||
gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20190222213804-5cb15d344471 h1:MzQGt8qWQCR+39kbYRd0uQqsvSidpYqJLFeWiJ9l4OE=
|
||||
k8s.io/api v0.0.0-20190222213804-5cb15d344471/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
|
||||
k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628 h1:UYfHH+KEF88OTg+GojQUwFTNxbxwmoktLwutUzR0GPg=
|
||||
k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
|
||||
k8s.io/apiserver v0.0.0-20181213151703-3ccfe8365421 h1:NyOpnIh+7SLvC05NGCIXF9c4KhnkTZQE2SxF+m9otww=
|
||||
k8s.io/apiserver v0.0.0-20181213151703-3ccfe8365421/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w=
|
||||
k8s.io/client-go v10.0.0+incompatible h1:F1IqCqw7oMBzDkqlcBymRq1450wD0eNqLE9jzUrIi34=
|
||||
k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk=
|
||||
k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22 h1:f0BTap/vrgs21vVbJ1ySdsNtcivpA1x4ut6Wla9HKKw=
|
||||
k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22/go.mod h1:iU+ZGYsNlvU9XKUSso6SQfKTCCw7lFduMZy26Mgr2Fw=
|
||||
k8s.io/kubernetes v1.13.7 h1:6I48MdE69fo0SRopCAnxgBlUqhlMjeWWEA8Y3ThzUyA=
|
||||
k8s.io/kubernetes v1.13.7/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||
k8s.io/utils v0.0.0-20180801164400-045dc31ee5c4 h1:jx/N9qda/hFHvydYhYL9SZ6oh/vXekqK7YeIghe5cjI=
|
||||
k8s.io/utils v0.0.0-20180801164400-045dc31ee5c4/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190426204423-ea680f03cc65/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
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.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
|
||||
k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
|
||||
k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI=
|
||||
k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM=
|
||||
k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
|
||||
k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
|
||||
k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc=
|
||||
k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg=
|
||||
k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=
|
||||
k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
|
||||
k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4=
|
||||
k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU=
|
||||
k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M=
|
||||
k8s.io/klog/v2 v2.90.0/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/kubelet v0.26.1 h1:wQyCQYmLW6GN3v7gVTxnc3jAE4zMYDlzdF3FZV4rKas=
|
||||
k8s.io/kubelet v0.26.1/go.mod h1:gFVZ1Ab4XdjtnYdVRATwGwku7FhTxo6LVEZwYoQaDT8=
|
||||
k8s.io/utils v0.0.0-20230202215443-34013725500c h1:YVqDar2X7YiQa/DVAXFMDIfGF8uGrHQemlrwRU5NlVI=
|
||||
k8s.io/utils v0.0.0-20230202215443-34013725500c/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35 h1:+xBL5uTc+BkPBwmMi3vYfUJjq+N3K+H6PXeETwf5cPI=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo=
|
||||
sigs.k8s.io/controller-runtime v0.14.4 h1:Kd/Qgx5pd2XUL08eOV2vwIq3L9GhIbJ5Nxengbd4/0M=
|
||||
sigs.k8s.io/controller-runtime v0.14.4/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
|
||||
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=
|
||||
|
||||
@@ -56,6 +56,14 @@ rules:
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- get
|
||||
- create
|
||||
- update
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
|
||||
@@ -4,6 +4,8 @@ metadata:
|
||||
name: vkubelet-mock-0
|
||||
spec:
|
||||
containers:
|
||||
- name: jaeger-tracing
|
||||
image: jaegertracing/all-in-one:1.22
|
||||
- name: vkubelet-mock-0
|
||||
image: virtual-kubelet
|
||||
# "IfNotPresent" is used to prevent Minikube from trying to pull from the registry (and failing) in the first place.
|
||||
@@ -23,18 +25,16 @@ spec:
|
||||
- --klog.logtostderr
|
||||
- --log-level
|
||||
- debug
|
||||
- --trace-exporter
|
||||
- jaeger
|
||||
- --trace-sample-rate=always
|
||||
env:
|
||||
- name: JAEGER_AGENT_ENDPOINT
|
||||
value: localhost:6831
|
||||
- name: KUBELET_PORT
|
||||
value: "10250"
|
||||
- name: VKUBELET_POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
ports:
|
||||
- name: metrics
|
||||
containerPort: 10255
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /stats/summary
|
||||
port: metrics
|
||||
serviceAccountName: virtual-kubelet
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: skaffold/v1alpha5
|
||||
apiVersion: skaffold/v2beta10
|
||||
kind: Config
|
||||
build:
|
||||
artifacts:
|
||||
|
||||
27
internal/expansion/LICENSE
Normal file
27
internal/expansion/LICENSE
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
4
internal/expansion/README.md
Normal file
4
internal/expansion/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Copied from
|
||||
https://github.com/kubernetes/kubernetes/tree/master/third_party/forked/golang/expansion .
|
||||
|
||||
This is to eliminate a direct dependency on kubernetes/kubernetes.
|
||||
102
internal/expansion/expand.go
Normal file
102
internal/expansion/expand.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package expansion
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
const (
|
||||
operator = '$'
|
||||
referenceOpener = '('
|
||||
referenceCloser = ')'
|
||||
)
|
||||
|
||||
// syntaxWrap returns the input string wrapped by the expansion syntax.
|
||||
func syntaxWrap(input string) string {
|
||||
return string(operator) + string(referenceOpener) + input + string(referenceCloser)
|
||||
}
|
||||
|
||||
// MappingFuncFor returns a mapping function for use with Expand that
|
||||
// implements the expansion semantics defined in the expansion spec; it
|
||||
// returns the input string wrapped in the expansion syntax if no mapping
|
||||
// for the input is found.
|
||||
func MappingFuncFor(context ...map[string]string) func(string) string {
|
||||
return func(input string) string {
|
||||
for _, vars := range context {
|
||||
val, ok := vars[input]
|
||||
if ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
return syntaxWrap(input)
|
||||
}
|
||||
}
|
||||
|
||||
// Expand replaces variable references in the input string according to
|
||||
// the expansion spec using the given mapping function to resolve the
|
||||
// values of variables.
|
||||
func Expand(input string, mapping func(string) string) string {
|
||||
var buf bytes.Buffer
|
||||
checkpoint := 0
|
||||
for cursor := 0; cursor < len(input); cursor++ {
|
||||
if input[cursor] == operator && cursor+1 < len(input) {
|
||||
// Copy the portion of the input string since the last
|
||||
// checkpoint into the buffer
|
||||
buf.WriteString(input[checkpoint:cursor])
|
||||
|
||||
// Attempt to read the variable name as defined by the
|
||||
// syntax from the input string
|
||||
read, isVar, advance := tryReadVariableName(input[cursor+1:])
|
||||
|
||||
if isVar {
|
||||
// We were able to read a variable name correctly;
|
||||
// apply the mapping to the variable name and copy the
|
||||
// bytes into the buffer
|
||||
buf.WriteString(mapping(read))
|
||||
} else {
|
||||
// Not a variable name; copy the read bytes into the buffer
|
||||
buf.WriteString(read)
|
||||
}
|
||||
|
||||
// Advance the cursor in the input string to account for
|
||||
// bytes consumed to read the variable name expression
|
||||
cursor += advance
|
||||
|
||||
// Advance the checkpoint in the input string
|
||||
checkpoint = cursor + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Return the buffer and any remaining unwritten bytes in the
|
||||
// input string.
|
||||
return buf.String() + input[checkpoint:]
|
||||
}
|
||||
|
||||
// tryReadVariableName attempts to read a variable name from the input
|
||||
// string and returns the content read from the input, whether that content
|
||||
// represents a variable name to perform mapping on, and the number of bytes
|
||||
// consumed in the input string.
|
||||
//
|
||||
// The input string is assumed not to contain the initial operator.
|
||||
func tryReadVariableName(input string) (string, bool, int) {
|
||||
switch input[0] {
|
||||
case operator:
|
||||
// Escaped operator; return it.
|
||||
return input[0:1], false, 1
|
||||
case referenceOpener:
|
||||
// Scan to expression closer
|
||||
for i := 1; i < len(input); i++ {
|
||||
if input[i] == referenceCloser {
|
||||
return input[1:i], true, i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Incomplete reference; return it.
|
||||
return string(operator) + string(referenceOpener), false, 1
|
||||
default:
|
||||
// Not the beginning of an expression, ie, an operator
|
||||
// that doesn't begin an expression. Return the operator
|
||||
// and the first rune in the string.
|
||||
return (string(operator) + string(input[0])), false, 1
|
||||
}
|
||||
}
|
||||
281
internal/expansion/expand_test.go
Normal file
281
internal/expansion/expand_test.go
Normal file
@@ -0,0 +1,281 @@
|
||||
package expansion
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMapReference(t *testing.T) {
|
||||
// We use a struct here instead of a map because we need mappings to happen in order.
|
||||
// Go maps are randomized.
|
||||
type envVar struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
envs := []envVar{
|
||||
{"FOO", "bar"},
|
||||
{"ZOO", "$(FOO)-1"},
|
||||
{"BLU", "$(ZOO)-2"},
|
||||
}
|
||||
|
||||
declaredEnv := map[string]string{
|
||||
"FOO": "bar",
|
||||
"ZOO": "$(FOO)-1",
|
||||
"BLU": "$(ZOO)-2",
|
||||
}
|
||||
|
||||
serviceEnv := map[string]string{}
|
||||
|
||||
mapping := MappingFuncFor(declaredEnv, serviceEnv)
|
||||
|
||||
for _, env := range envs {
|
||||
declaredEnv[env.Name] = Expand(env.Value, mapping)
|
||||
}
|
||||
|
||||
expectedEnv := map[string]string{
|
||||
"FOO": "bar",
|
||||
"ZOO": "bar-1",
|
||||
"BLU": "bar-1-2",
|
||||
}
|
||||
|
||||
for k, v := range expectedEnv {
|
||||
if e, a := v, declaredEnv[k]; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
} else {
|
||||
delete(declaredEnv, k)
|
||||
}
|
||||
}
|
||||
|
||||
if len(declaredEnv) != 0 {
|
||||
t.Errorf("Unexpected keys in declared env: %v", declaredEnv)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapping(t *testing.T) {
|
||||
context := map[string]string{
|
||||
"VAR_A": "A",
|
||||
"VAR_B": "B",
|
||||
"VAR_C": "C",
|
||||
"VAR_REF": "$(VAR_A)",
|
||||
"VAR_EMPTY": "",
|
||||
}
|
||||
mapping := MappingFuncFor(context)
|
||||
|
||||
doExpansionTest(t, mapping)
|
||||
}
|
||||
|
||||
func TestMappingDual(t *testing.T) {
|
||||
context := map[string]string{
|
||||
"VAR_A": "A",
|
||||
"VAR_EMPTY": "",
|
||||
}
|
||||
context2 := map[string]string{
|
||||
"VAR_B": "B",
|
||||
"VAR_C": "C",
|
||||
"VAR_REF": "$(VAR_A)",
|
||||
}
|
||||
mapping := MappingFuncFor(context, context2)
|
||||
|
||||
doExpansionTest(t, mapping)
|
||||
}
|
||||
|
||||
func doExpansionTest(t *testing.T, mapping func(string) string) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "whole string",
|
||||
input: "$(VAR_A)",
|
||||
expected: "A",
|
||||
},
|
||||
{
|
||||
name: "repeat",
|
||||
input: "$(VAR_A)-$(VAR_A)",
|
||||
expected: "A-A",
|
||||
},
|
||||
{
|
||||
name: "beginning",
|
||||
input: "$(VAR_A)-1",
|
||||
expected: "A-1",
|
||||
},
|
||||
{
|
||||
name: "middle",
|
||||
input: "___$(VAR_B)___",
|
||||
expected: "___B___",
|
||||
},
|
||||
{
|
||||
name: "end",
|
||||
input: "___$(VAR_C)",
|
||||
expected: "___C",
|
||||
},
|
||||
{
|
||||
name: "compound",
|
||||
input: "$(VAR_A)_$(VAR_B)_$(VAR_C)",
|
||||
expected: "A_B_C",
|
||||
},
|
||||
{
|
||||
name: "escape & expand",
|
||||
input: "$$(VAR_B)_$(VAR_A)",
|
||||
expected: "$(VAR_B)_A",
|
||||
},
|
||||
{
|
||||
name: "compound escape",
|
||||
input: "$$(VAR_A)_$$(VAR_B)",
|
||||
expected: "$(VAR_A)_$(VAR_B)",
|
||||
},
|
||||
{
|
||||
name: "mixed in escapes",
|
||||
input: "f000-$$VAR_A",
|
||||
expected: "f000-$VAR_A",
|
||||
},
|
||||
{
|
||||
name: "backslash escape ignored",
|
||||
input: "foo\\$(VAR_C)bar",
|
||||
expected: "foo\\Cbar",
|
||||
},
|
||||
{
|
||||
name: "backslash escape ignored",
|
||||
input: "foo\\\\$(VAR_C)bar",
|
||||
expected: "foo\\\\Cbar",
|
||||
},
|
||||
{
|
||||
name: "lots of backslashes",
|
||||
input: "foo\\\\\\\\$(VAR_A)bar",
|
||||
expected: "foo\\\\\\\\Abar",
|
||||
},
|
||||
{
|
||||
name: "nested var references",
|
||||
input: "$(VAR_A$(VAR_B))",
|
||||
expected: "$(VAR_A$(VAR_B))",
|
||||
},
|
||||
{
|
||||
name: "nested var references second type",
|
||||
input: "$(VAR_A$(VAR_B)",
|
||||
expected: "$(VAR_A$(VAR_B)",
|
||||
},
|
||||
{
|
||||
name: "value is a reference",
|
||||
input: "$(VAR_REF)",
|
||||
expected: "$(VAR_A)",
|
||||
},
|
||||
{
|
||||
name: "value is a reference x 2",
|
||||
input: "%%$(VAR_REF)--$(VAR_REF)%%",
|
||||
expected: "%%$(VAR_A)--$(VAR_A)%%",
|
||||
},
|
||||
{
|
||||
name: "empty var",
|
||||
input: "foo$(VAR_EMPTY)bar",
|
||||
expected: "foobar",
|
||||
},
|
||||
{
|
||||
name: "unterminated expression",
|
||||
input: "foo$(VAR_Awhoops!",
|
||||
expected: "foo$(VAR_Awhoops!",
|
||||
},
|
||||
{
|
||||
name: "expression without operator",
|
||||
input: "f00__(VAR_A)__",
|
||||
expected: "f00__(VAR_A)__",
|
||||
},
|
||||
{
|
||||
name: "shell special vars pass through",
|
||||
input: "$?_boo_$!",
|
||||
expected: "$?_boo_$!",
|
||||
},
|
||||
{
|
||||
name: "bare operators are ignored",
|
||||
input: "$VAR_A",
|
||||
expected: "$VAR_A",
|
||||
},
|
||||
{
|
||||
name: "undefined vars are passed through",
|
||||
input: "$(VAR_DNE)",
|
||||
expected: "$(VAR_DNE)",
|
||||
},
|
||||
{
|
||||
name: "multiple (even) operators, var undefined",
|
||||
input: "$$$$$$(BIG_MONEY)",
|
||||
expected: "$$$(BIG_MONEY)",
|
||||
},
|
||||
{
|
||||
name: "multiple (even) operators, var defined",
|
||||
input: "$$$$$$(VAR_A)",
|
||||
expected: "$$$(VAR_A)",
|
||||
},
|
||||
{
|
||||
name: "multiple (odd) operators, var undefined",
|
||||
input: "$$$$$$$(GOOD_ODDS)",
|
||||
expected: "$$$$(GOOD_ODDS)",
|
||||
},
|
||||
{
|
||||
name: "multiple (odd) operators, var defined",
|
||||
input: "$$$$$$$(VAR_A)",
|
||||
expected: "$$$A",
|
||||
},
|
||||
{
|
||||
name: "missing open expression",
|
||||
input: "$VAR_A)",
|
||||
expected: "$VAR_A)",
|
||||
},
|
||||
{
|
||||
name: "shell syntax ignored",
|
||||
input: "${VAR_A}",
|
||||
expected: "${VAR_A}",
|
||||
},
|
||||
{
|
||||
name: "trailing incomplete expression not consumed",
|
||||
input: "$(VAR_B)_______$(A",
|
||||
expected: "B_______$(A",
|
||||
},
|
||||
{
|
||||
name: "trailing incomplete expression, no content, is not consumed",
|
||||
input: "$(VAR_C)_______$(",
|
||||
expected: "C_______$(",
|
||||
},
|
||||
{
|
||||
name: "operator at end of input string is preserved",
|
||||
input: "$(VAR_A)foobarzab$",
|
||||
expected: "Afoobarzab$",
|
||||
},
|
||||
{
|
||||
name: "shell escaped incomplete expr",
|
||||
input: "foo-\\$(VAR_A",
|
||||
expected: "foo-\\$(VAR_A",
|
||||
},
|
||||
{
|
||||
name: "lots of $( in middle",
|
||||
input: "--$($($($($--",
|
||||
expected: "--$($($($($--",
|
||||
},
|
||||
{
|
||||
name: "lots of $( in beginning",
|
||||
input: "$($($($($--foo$(",
|
||||
expected: "$($($($($--foo$(",
|
||||
},
|
||||
{
|
||||
name: "lots of $( at end",
|
||||
input: "foo0--$($($($(",
|
||||
expected: "foo0--$($($($(",
|
||||
},
|
||||
{
|
||||
name: "escaped operators in variable names are not escaped",
|
||||
input: "$(foo$$var)",
|
||||
expected: "$(foo$$var)",
|
||||
},
|
||||
{
|
||||
name: "newline not expanded",
|
||||
input: "\n",
|
||||
expected: "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
expanded := Expand(tc.input, mapping)
|
||||
if e, a := tc.expected, expanded; e != a {
|
||||
t.Errorf("%v: expected %q, got %q", tc.name, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
3
internal/kubernetes/README.md
Normal file
3
internal/kubernetes/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
These package are copied from upstream kubernetes.
|
||||
|
||||
They are here to prevent a dep on the whole of kubernetes/kubernetes.
|
||||
79
internal/kubernetes/remotecommand/exec.go
Normal file
79
internal/kubernetes/remotecommand/exec.go
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
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 remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
utilexec "k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
// Executor knows how to execute a command in a container in a pod.
|
||||
type Executor interface {
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error
|
||||
}
|
||||
|
||||
// ServeExec handles requests to execute a command in a container. After
|
||||
// creating/receiving the required streams, it delegates the actual execution
|
||||
// to the executor.
|
||||
func ServeExec(w http.ResponseWriter, req *http.Request, executor Executor, podName string, uid types.UID, container string, cmd []string, streamOpts *Options, idleTimeout, streamCreationTimeout time.Duration, supportedProtocols []string) {
|
||||
ctx, ok := createStreams(req, w, streamOpts, supportedProtocols, idleTimeout, streamCreationTimeout)
|
||||
if !ok {
|
||||
// error is handled by createStreams
|
||||
return
|
||||
}
|
||||
defer ctx.conn.Close()
|
||||
|
||||
err := executor.ExecInContainer(podName, uid, container, cmd, ctx.stdinStream, ctx.stdoutStream, ctx.stderrStream, ctx.tty, ctx.resizeChan, 0)
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(utilexec.ExitError); ok && exitErr.Exited() {
|
||||
rc := exitErr.ExitStatus()
|
||||
ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Reason: remotecommandconsts.NonZeroExitCodeReason,
|
||||
Details: &metav1.StatusDetails{
|
||||
Causes: []metav1.StatusCause{
|
||||
{
|
||||
Type: remotecommandconsts.ExitCodeCauseType,
|
||||
Message: fmt.Sprintf("%d", rc),
|
||||
},
|
||||
},
|
||||
},
|
||||
Message: fmt.Sprintf("command terminated with non-zero exit code: %v", exitErr),
|
||||
}})
|
||||
} else {
|
||||
err = fmt.Errorf("error executing command in container: %v", err)
|
||||
runtime.HandleError(err)
|
||||
ctx.writeStatus(apierrors.NewInternalError(err))
|
||||
}
|
||||
} else {
|
||||
ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusSuccess,
|
||||
}})
|
||||
}
|
||||
}
|
||||
447
internal/kubernetes/remotecommand/httpstream.go
Normal file
447
internal/kubernetes/remotecommand/httpstream.go
Normal file
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
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 remotecommand
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Options contains details about which streams are required for
|
||||
// remote command execution.
|
||||
type Options struct {
|
||||
Stdin bool
|
||||
Stdout bool
|
||||
Stderr bool
|
||||
TTY bool
|
||||
}
|
||||
|
||||
// NewOptions creates a new Options from the Request.
|
||||
func NewOptions(req *http.Request) (*Options, error) {
|
||||
tty := req.FormValue(api.ExecTTYParam) == "1"
|
||||
stdin := req.FormValue(api.ExecStdinParam) == "1"
|
||||
stdout := req.FormValue(api.ExecStdoutParam) == "1"
|
||||
stderr := req.FormValue(api.ExecStderrParam) == "1"
|
||||
if tty && stderr {
|
||||
// TODO: make this an error before we reach this method
|
||||
klog.V(4).Infof("Access to exec with tty and stderr is not supported, bypassing stderr")
|
||||
stderr = false
|
||||
}
|
||||
|
||||
if !stdin && !stdout && !stderr {
|
||||
return nil, fmt.Errorf("you must specify at least 1 of stdin, stdout, stderr")
|
||||
}
|
||||
|
||||
return &Options{
|
||||
Stdin: stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
TTY: tty,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// context contains the connection and streams used when
|
||||
// forwarding an attach or execute session into a container.
|
||||
type context struct {
|
||||
conn io.Closer
|
||||
stdinStream io.ReadCloser
|
||||
stdoutStream io.WriteCloser
|
||||
stderrStream io.WriteCloser
|
||||
writeStatus func(status *apierrors.StatusError) error
|
||||
resizeStream io.ReadCloser
|
||||
resizeChan chan remotecommand.TerminalSize
|
||||
tty bool
|
||||
}
|
||||
|
||||
// streamAndReply holds both a Stream and a channel that is closed when the stream's reply frame is
|
||||
// enqueued. Consumers can wait for replySent to be closed prior to proceeding, to ensure that the
|
||||
// replyFrame is enqueued before the connection's goaway frame is sent (e.g. if a stream was
|
||||
// received and right after, the connection gets closed).
|
||||
type streamAndReply struct {
|
||||
httpstream.Stream
|
||||
replySent <-chan struct{}
|
||||
}
|
||||
|
||||
// waitStreamReply waits until either replySent or stop is closed. If replySent is closed, it sends
|
||||
// an empty struct to the notify channel.
|
||||
func waitStreamReply(replySent <-chan struct{}, notify chan<- struct{}, stop <-chan struct{}) {
|
||||
select {
|
||||
case <-replySent:
|
||||
notify <- struct{}{}
|
||||
case <-stop:
|
||||
}
|
||||
}
|
||||
|
||||
func createStreams(req *http.Request, w http.ResponseWriter, opts *Options, supportedStreamProtocols []string, idleTimeout, streamCreationTimeout time.Duration) (*context, bool) {
|
||||
var ctx *context
|
||||
var ok bool
|
||||
if wsstream.IsWebSocketRequest(req) {
|
||||
ctx, ok = createWebSocketStreams(req, w, opts, idleTimeout)
|
||||
} else {
|
||||
ctx, ok = createHTTPStreamStreams(req, w, opts, supportedStreamProtocols, idleTimeout, streamCreationTimeout)
|
||||
}
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if ctx.resizeStream != nil {
|
||||
ctx.resizeChan = make(chan remotecommand.TerminalSize)
|
||||
go handleResizeEvents(ctx.resizeStream, ctx.resizeChan)
|
||||
}
|
||||
|
||||
return ctx, true
|
||||
}
|
||||
|
||||
func createHTTPStreamStreams(req *http.Request, w http.ResponseWriter, opts *Options, supportedStreamProtocols []string, idleTimeout, streamCreationTimeout time.Duration) (*context, bool) {
|
||||
protocol, err := httpstream.Handshake(req, w, supportedStreamProtocols)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
streamCh := make(chan streamAndReply)
|
||||
|
||||
upgrader := spdy.NewResponseUpgrader()
|
||||
conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream, replySent <-chan struct{}) error {
|
||||
streamCh <- streamAndReply{Stream: stream, replySent: replySent}
|
||||
return nil
|
||||
})
|
||||
// from this point on, we can no longer call methods on response
|
||||
if conn == nil {
|
||||
// The upgrader is responsible for notifying the client of any errors that
|
||||
// occurred during upgrading. All we can do is return here at this point
|
||||
// if we weren't successful in upgrading.
|
||||
return nil, false
|
||||
}
|
||||
|
||||
conn.SetIdleTimeout(idleTimeout)
|
||||
|
||||
var handler protocolHandler
|
||||
switch protocol {
|
||||
case remotecommandconsts.StreamProtocolV4Name:
|
||||
handler = &v4ProtocolHandler{}
|
||||
case remotecommandconsts.StreamProtocolV3Name:
|
||||
handler = &v3ProtocolHandler{}
|
||||
case remotecommandconsts.StreamProtocolV2Name:
|
||||
handler = &v2ProtocolHandler{}
|
||||
case "":
|
||||
klog.V(4).Infof("Client did not request protocol negotiation. Falling back to %q", remotecommandconsts.StreamProtocolV1Name)
|
||||
fallthrough
|
||||
case remotecommandconsts.StreamProtocolV1Name:
|
||||
handler = &v1ProtocolHandler{}
|
||||
}
|
||||
|
||||
// count the streams client asked for, starting with 1
|
||||
expectedStreams := 1
|
||||
if opts.Stdin {
|
||||
expectedStreams++
|
||||
}
|
||||
if opts.Stdout {
|
||||
expectedStreams++
|
||||
}
|
||||
if opts.Stderr {
|
||||
expectedStreams++
|
||||
}
|
||||
if opts.TTY && handler.supportsTerminalResizing() {
|
||||
expectedStreams++
|
||||
}
|
||||
|
||||
expired := time.NewTimer(streamCreationTimeout)
|
||||
defer expired.Stop()
|
||||
|
||||
ctx, err := handler.waitForStreams(streamCh, expectedStreams, expired.C)
|
||||
if err != nil {
|
||||
runtime.HandleError(err)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
ctx.conn = conn
|
||||
ctx.tty = opts.TTY
|
||||
|
||||
return ctx, true
|
||||
}
|
||||
|
||||
type protocolHandler interface {
|
||||
// waitForStreams waits for the expected streams or a timeout, returning a
|
||||
// remoteCommandContext if all the streams were received, or an error if not.
|
||||
waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error)
|
||||
// supportsTerminalResizing returns true if the protocol handler supports terminal resizing
|
||||
supportsTerminalResizing() bool
|
||||
}
|
||||
|
||||
// v4ProtocolHandler implements the V4 protocol version for streaming command execution. It only differs
|
||||
// in from v3 in the error stream format using an json-marshaled metav1.Status which carries
|
||||
// the process' exit code.
|
||||
type v4ProtocolHandler struct{}
|
||||
|
||||
func (*v4ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) {
|
||||
ctx := &context{}
|
||||
receivedStreams := 0
|
||||
replyChan := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
WaitForStreams:
|
||||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
ctx.writeStatus = v4WriteStatusFunc(stream) // write json errors
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeResize:
|
||||
ctx.resizeStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
runtime.HandleError(fmt.Errorf("unexpected stream type: %q", streamType))
|
||||
}
|
||||
case <-replyChan:
|
||||
receivedStreams++
|
||||
if receivedStreams == expectedStreams {
|
||||
break WaitForStreams
|
||||
}
|
||||
case <-expired:
|
||||
// TODO find a way to return the error to the user. Maybe use a separate
|
||||
// stream to report errors?
|
||||
return nil, errors.New("timed out waiting for client to create streams")
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// supportsTerminalResizing returns true because v4ProtocolHandler supports it
|
||||
func (*v4ProtocolHandler) supportsTerminalResizing() bool { return true }
|
||||
|
||||
// v3ProtocolHandler implements the V3 protocol version for streaming command execution.
|
||||
type v3ProtocolHandler struct{}
|
||||
|
||||
func (*v3ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) {
|
||||
ctx := &context{}
|
||||
receivedStreams := 0
|
||||
replyChan := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
WaitForStreams:
|
||||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
ctx.writeStatus = v1WriteStatusFunc(stream)
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeResize:
|
||||
ctx.resizeStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
runtime.HandleError(fmt.Errorf("unexpected stream type: %q", streamType))
|
||||
}
|
||||
case <-replyChan:
|
||||
receivedStreams++
|
||||
if receivedStreams == expectedStreams {
|
||||
break WaitForStreams
|
||||
}
|
||||
case <-expired:
|
||||
// TODO find a way to return the error to the user. Maybe use a separate
|
||||
// stream to report errors?
|
||||
return nil, errors.New("timed out waiting for client to create streams")
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// supportsTerminalResizing returns true because v3ProtocolHandler supports it
|
||||
func (*v3ProtocolHandler) supportsTerminalResizing() bool { return true }
|
||||
|
||||
// v2ProtocolHandler implements the V2 protocol version for streaming command execution.
|
||||
type v2ProtocolHandler struct{}
|
||||
|
||||
func (*v2ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) {
|
||||
ctx := &context{}
|
||||
receivedStreams := 0
|
||||
replyChan := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
WaitForStreams:
|
||||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
ctx.writeStatus = v1WriteStatusFunc(stream)
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
runtime.HandleError(fmt.Errorf("unexpected stream type: %q", streamType))
|
||||
}
|
||||
case <-replyChan:
|
||||
receivedStreams++
|
||||
if receivedStreams == expectedStreams {
|
||||
break WaitForStreams
|
||||
}
|
||||
case <-expired:
|
||||
// TODO find a way to return the error to the user. Maybe use a separate
|
||||
// stream to report errors?
|
||||
return nil, errors.New("timed out waiting for client to create streams")
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// supportsTerminalResizing returns false because v2ProtocolHandler doesn't support it.
|
||||
func (*v2ProtocolHandler) supportsTerminalResizing() bool { return false }
|
||||
|
||||
// v1ProtocolHandler implements the V1 protocol version for streaming command execution.
|
||||
type v1ProtocolHandler struct{}
|
||||
|
||||
func (*v1ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) {
|
||||
ctx := &context{}
|
||||
receivedStreams := 0
|
||||
replyChan := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
WaitForStreams:
|
||||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
ctx.writeStatus = v1WriteStatusFunc(stream)
|
||||
|
||||
// This defer statement shouldn't be here, but due to previous refactoring, it ended up in
|
||||
// here. This is what 1.0.x kubelets do, so we're retaining that behavior. This is fixed in
|
||||
// the v2ProtocolHandler.
|
||||
defer stream.Reset()
|
||||
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
runtime.HandleError(fmt.Errorf("unexpected stream type: %q", streamType))
|
||||
}
|
||||
case <-replyChan:
|
||||
receivedStreams++
|
||||
if receivedStreams == expectedStreams {
|
||||
break WaitForStreams
|
||||
}
|
||||
case <-expired:
|
||||
// TODO find a way to return the error to the user. Maybe use a separate
|
||||
// stream to report errors?
|
||||
return nil, errors.New("timed out waiting for client to create streams")
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.stdinStream != nil {
|
||||
ctx.stdinStream.Close()
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// supportsTerminalResizing returns false because v1ProtocolHandler doesn't support it.
|
||||
func (*v1ProtocolHandler) supportsTerminalResizing() bool { return false }
|
||||
|
||||
func handleResizeEvents(stream io.Reader, channel chan<- remotecommand.TerminalSize) {
|
||||
defer runtime.HandleCrash()
|
||||
defer close(channel)
|
||||
|
||||
decoder := json.NewDecoder(stream)
|
||||
for {
|
||||
size := remotecommand.TerminalSize{}
|
||||
if err := decoder.Decode(&size); err != nil {
|
||||
break
|
||||
}
|
||||
channel <- size
|
||||
}
|
||||
}
|
||||
|
||||
func v1WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) error {
|
||||
return func(status *apierrors.StatusError) error {
|
||||
if status.Status().Status == metav1.StatusSuccess {
|
||||
return nil // send error messages
|
||||
}
|
||||
_, err := stream.Write([]byte(status.Error()))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// v4WriteStatusFunc returns a WriteStatusFunc that marshals a given api Status
|
||||
// as json in the error channel.
|
||||
func v4WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) error {
|
||||
return func(status *apierrors.StatusError) error {
|
||||
bs, err := json.Marshal(status.Status())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = stream.Write(bs)
|
||||
return err
|
||||
}
|
||||
}
|
||||
132
internal/kubernetes/remotecommand/websocket.go
Normal file
132
internal/kubernetes/remotecommand/websocket.go
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
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 remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/server/httplog"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
)
|
||||
|
||||
const (
|
||||
stdinChannel = iota
|
||||
stdoutChannel
|
||||
stderrChannel
|
||||
errorChannel
|
||||
resizeChannel
|
||||
|
||||
preV4BinaryWebsocketProtocol = wsstream.ChannelWebSocketProtocol
|
||||
preV4Base64WebsocketProtocol = wsstream.Base64ChannelWebSocketProtocol
|
||||
v4BinaryWebsocketProtocol = "v4." + wsstream.ChannelWebSocketProtocol
|
||||
v4Base64WebsocketProtocol = "v4." + wsstream.Base64ChannelWebSocketProtocol
|
||||
)
|
||||
|
||||
// createChannels returns the standard channel types for a shell connection (STDIN 0, STDOUT 1, STDERR 2)
|
||||
// along with the approximate duplex value. It also creates the error (3) and resize (4) channels.
|
||||
func createChannels(opts *Options) []wsstream.ChannelType {
|
||||
// open the requested channels, and always open the error channel
|
||||
channels := make([]wsstream.ChannelType, 5)
|
||||
channels[stdinChannel] = readChannel(opts.Stdin)
|
||||
channels[stdoutChannel] = writeChannel(opts.Stdout)
|
||||
channels[stderrChannel] = writeChannel(opts.Stderr)
|
||||
channels[errorChannel] = wsstream.WriteChannel
|
||||
channels[resizeChannel] = wsstream.ReadChannel
|
||||
return channels
|
||||
}
|
||||
|
||||
// readChannel returns wsstream.ReadChannel if real is true, or wsstream.IgnoreChannel.
|
||||
func readChannel(real bool) wsstream.ChannelType {
|
||||
if real {
|
||||
return wsstream.ReadChannel
|
||||
}
|
||||
return wsstream.IgnoreChannel
|
||||
}
|
||||
|
||||
// writeChannel returns wsstream.WriteChannel if real is true, or wsstream.IgnoreChannel.
|
||||
func writeChannel(real bool) wsstream.ChannelType {
|
||||
if real {
|
||||
return wsstream.WriteChannel
|
||||
}
|
||||
return wsstream.IgnoreChannel
|
||||
}
|
||||
|
||||
// createWebSocketStreams returns a context containing the websocket connection and
|
||||
// streams needed to perform an exec or an attach.
|
||||
func createWebSocketStreams(req *http.Request, w http.ResponseWriter, opts *Options, idleTimeout time.Duration) (*context, bool) {
|
||||
channels := createChannels(opts)
|
||||
conn := wsstream.NewConn(map[string]wsstream.ChannelProtocolConfig{
|
||||
"": {
|
||||
Binary: true,
|
||||
Channels: channels,
|
||||
},
|
||||
preV4BinaryWebsocketProtocol: {
|
||||
Binary: true,
|
||||
Channels: channels,
|
||||
},
|
||||
preV4Base64WebsocketProtocol: {
|
||||
Binary: false,
|
||||
Channels: channels,
|
||||
},
|
||||
v4BinaryWebsocketProtocol: {
|
||||
Binary: true,
|
||||
Channels: channels,
|
||||
},
|
||||
v4Base64WebsocketProtocol: {
|
||||
Binary: false,
|
||||
Channels: channels,
|
||||
},
|
||||
})
|
||||
conn.SetIdleTimeout(idleTimeout)
|
||||
negotiatedProtocol, streams, err := conn.Open(httplog.Unlogged(req, w), req)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("unable to upgrade websocket connection: %v", err))
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Send an empty message to the lowest writable channel to notify the client the connection is established
|
||||
// TODO: make generic to SPDY and WebSockets and do it outside of this method?
|
||||
switch {
|
||||
case opts.Stdout:
|
||||
streams[stdoutChannel].Write([]byte{})
|
||||
case opts.Stderr:
|
||||
streams[stderrChannel].Write([]byte{})
|
||||
default:
|
||||
streams[errorChannel].Write([]byte{})
|
||||
}
|
||||
|
||||
ctx := &context{
|
||||
conn: conn,
|
||||
stdinStream: streams[stdinChannel],
|
||||
stdoutStream: streams[stdoutChannel],
|
||||
stderrStream: streams[stderrChannel],
|
||||
tty: opts.TTY,
|
||||
resizeStream: streams[resizeChannel],
|
||||
}
|
||||
|
||||
switch negotiatedProtocol {
|
||||
case v4BinaryWebsocketProtocol, v4Base64WebsocketProtocol:
|
||||
ctx.writeStatus = v4WriteStatusFunc(streams[errorChannel])
|
||||
default:
|
||||
ctx.writeStatus = v1WriteStatusFunc(streams[errorChannel])
|
||||
}
|
||||
|
||||
return ctx, true
|
||||
}
|
||||
100
internal/lock/monitor.go
Normal file
100
internal/lock/monitor.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package lock
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewMonitorVariable instantiates an empty monitor variable
|
||||
func NewMonitorVariable() MonitorVariable {
|
||||
mv := &monitorVariable{
|
||||
versionInvalidationChannel: make(chan struct{}),
|
||||
}
|
||||
return mv
|
||||
}
|
||||
|
||||
// MonitorVariable is a specific monitor variable which allows for channel-subscription to changes to
|
||||
// the internal value of the MonitorVariable.
|
||||
type MonitorVariable interface {
|
||||
Set(value interface{})
|
||||
Subscribe() Subscription
|
||||
}
|
||||
|
||||
// Subscription is not concurrency safe. It must not be shared between multiple goroutines.
|
||||
type Subscription interface {
|
||||
// On instantiation, if the value has been set, this will return a closed channel. Otherwise, it will follow the
|
||||
// standard semantic, which is when the Monitor Variable is updated, this channel will close. The channel is updated
|
||||
// based on reading Value(). Once a value is read, the channel returned will only be closed if a the Monitor Variable
|
||||
// is set to a new value.
|
||||
NewValueReady() <-chan struct{}
|
||||
// Value returns a value object in a non-blocking fashion. This also means it may return an uninitialized value.
|
||||
// If the monitor variable has not yet been set, the "Version" of the value will be 0.
|
||||
Value() Value
|
||||
}
|
||||
|
||||
// Value contains the last set value from Set(). If the value is unset the version will be 0, and the value will be
|
||||
// nil.
|
||||
type Value struct {
|
||||
Value interface{}
|
||||
Version int64
|
||||
}
|
||||
|
||||
type monitorVariable struct {
|
||||
lock sync.Mutex
|
||||
currentValue interface{}
|
||||
// 0 indicates uninitialized
|
||||
currentVersion int64
|
||||
versionInvalidationChannel chan struct{}
|
||||
}
|
||||
|
||||
func (m *monitorVariable) Set(newValue interface{}) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.currentValue = newValue
|
||||
m.currentVersion++
|
||||
close(m.versionInvalidationChannel)
|
||||
m.versionInvalidationChannel = make(chan struct{})
|
||||
}
|
||||
|
||||
func (m *monitorVariable) Subscribe() Subscription {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
sub := &subscription{
|
||||
mv: m,
|
||||
}
|
||||
if m.currentVersion > 0 {
|
||||
// A value has been set. Set the first versionInvalidationChannel to a closed one.
|
||||
closedCh := make(chan struct{})
|
||||
close(closedCh)
|
||||
sub.lastVersionReadInvalidationChannel = closedCh
|
||||
} else {
|
||||
// The value hasn't yet been initialized.
|
||||
sub.lastVersionReadInvalidationChannel = m.versionInvalidationChannel
|
||||
}
|
||||
|
||||
return sub
|
||||
}
|
||||
|
||||
type subscription struct {
|
||||
mv *monitorVariable
|
||||
lastVersionRead int64
|
||||
lastVersionReadInvalidationChannel chan struct{}
|
||||
}
|
||||
|
||||
func (s *subscription) NewValueReady() <-chan struct{} {
|
||||
/* This lock could be finer grained (on just the subscription) */
|
||||
s.mv.lock.Lock()
|
||||
defer s.mv.lock.Unlock()
|
||||
return s.lastVersionReadInvalidationChannel
|
||||
}
|
||||
|
||||
func (s *subscription) Value() Value {
|
||||
s.mv.lock.Lock()
|
||||
defer s.mv.lock.Unlock()
|
||||
val := Value{
|
||||
Value: s.mv.currentValue,
|
||||
Version: s.mv.currentVersion,
|
||||
}
|
||||
s.lastVersionRead = s.mv.currentVersion
|
||||
s.lastVersionReadInvalidationChannel = s.mv.versionInvalidationChannel
|
||||
return val
|
||||
}
|
||||
111
internal/lock/monitor_test.go
Normal file
111
internal/lock/monitor_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package lock
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
"gotest.tools/assert"
|
||||
is "gotest.tools/assert/cmp"
|
||||
)
|
||||
|
||||
func TestMonitorUninitialized(t *testing.T) {
|
||||
t.Parallel()
|
||||
mv := NewMonitorVariable()
|
||||
subscription := mv.Subscribe()
|
||||
select {
|
||||
case <-subscription.NewValueReady():
|
||||
t.Fatalf("Received value update message: %v", subscription.Value())
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUninitialized(t *testing.T) {
|
||||
mv := NewMonitorVariable()
|
||||
subscription := mv.Subscribe()
|
||||
val := subscription.Value()
|
||||
assert.Assert(t, is.Equal(val.Version, int64(0)))
|
||||
}
|
||||
|
||||
func TestMonitorSetInitialVersionAfterListen(t *testing.T) {
|
||||
mv := NewMonitorVariable()
|
||||
subscription := mv.Subscribe()
|
||||
go mv.Set("test")
|
||||
<-subscription.NewValueReady()
|
||||
assert.Assert(t, is.Equal(subscription.Value().Value, "test"))
|
||||
}
|
||||
|
||||
func TestMonitorSetInitialVersionBeforeListen(t *testing.T) {
|
||||
mv := NewMonitorVariable()
|
||||
subscription := mv.Subscribe()
|
||||
mv.Set("test")
|
||||
<-subscription.NewValueReady()
|
||||
assert.Assert(t, is.Equal(subscription.Value().Value, "test"))
|
||||
}
|
||||
|
||||
func TestMonitorMultipleVersionsBlock(t *testing.T) {
|
||||
t.Parallel()
|
||||
mv := NewMonitorVariable()
|
||||
subscription := mv.Subscribe()
|
||||
mv.Set("test")
|
||||
<-subscription.NewValueReady()
|
||||
/* This should mark the "current" version as seen */
|
||||
val := subscription.Value()
|
||||
assert.Assert(t, is.Equal(val.Version, int64(1)))
|
||||
select {
|
||||
case <-subscription.NewValueReady():
|
||||
t.Fatalf("Received value update message: %v", subscription.Value())
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}
|
||||
func TestMonitorMultipleVersions(t *testing.T) {
|
||||
t.Parallel()
|
||||
lock := sync.Mutex{}
|
||||
lock.Lock()
|
||||
mv := NewMonitorVariable()
|
||||
triggers := []int{}
|
||||
ch := make(chan struct{}, 10)
|
||||
go func() {
|
||||
defer lock.Unlock()
|
||||
subscription := mv.Subscribe()
|
||||
for {
|
||||
// Lint is wrong, we need to call the function each time to get a fresh channel.
|
||||
<-subscription.NewValueReady()
|
||||
val := subscription.Value()
|
||||
triggers = append(triggers, val.Value.(int))
|
||||
ch <- struct{}{}
|
||||
if val.Value == 9 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
mv.Set(i)
|
||||
// Wait for the trigger to occur
|
||||
<-ch
|
||||
}
|
||||
|
||||
// Wait for the goroutine to finish
|
||||
lock.Lock()
|
||||
t.Logf("Saw %v triggers", triggers)
|
||||
assert.Assert(t, is.Len(triggers, 10))
|
||||
// Make sure we saw all 10 unique values
|
||||
assert.Assert(t, is.Equal(sets.NewInt(triggers...).Len(), 10))
|
||||
}
|
||||
func TestMonitorMultipleSubscribers(t *testing.T) {
|
||||
group := &errgroup.Group{}
|
||||
mv := NewMonitorVariable()
|
||||
for i := 0; i < 10; i++ {
|
||||
sub := mv.Subscribe()
|
||||
group.Go(func() error {
|
||||
<-sub.NewValueReady()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
mv.Set(1)
|
||||
_ = group.Wait()
|
||||
}
|
||||
@@ -17,13 +17,13 @@ package manager_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/internal/manager"
|
||||
testutil "github.com/virtual-kubelet/virtual-kubelet/internal/test/util"
|
||||
"gotest.tools/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
testutil "github.com/virtual-kubelet/virtual-kubelet/internal/test/util"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
)
|
||||
|
||||
// TestGetPods verifies that the resource manager acts as a passthrough to a pod lister.
|
||||
@@ -38,7 +38,7 @@ func TestGetPods(t *testing.T) {
|
||||
// Create a pod lister that will list the pods defined above.
|
||||
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
|
||||
for _, pod := range lsPods {
|
||||
indexer.Add(pod)
|
||||
assert.NilError(t, indexer.Add(pod))
|
||||
}
|
||||
podLister := corev1listers.NewPodLister(indexer)
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestGetSecret(t *testing.T) {
|
||||
// Create a secret lister that will list the secrets defined above.
|
||||
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
|
||||
for _, secret := range lsSecrets {
|
||||
indexer.Add(secret)
|
||||
assert.NilError(t, indexer.Add(secret))
|
||||
}
|
||||
secretLister := corev1listers.NewSecretLister(indexer)
|
||||
|
||||
@@ -106,7 +106,7 @@ func TestGetConfigMap(t *testing.T) {
|
||||
// Create a config map lister that will list the config maps defined above.
|
||||
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
|
||||
for _, secret := range lsConfigMaps {
|
||||
indexer.Add(secret)
|
||||
assert.NilError(t, indexer.Add(secret))
|
||||
}
|
||||
configMapLister := corev1listers.NewConfigMapLister(indexer)
|
||||
|
||||
@@ -123,7 +123,7 @@ func TestGetConfigMap(t *testing.T) {
|
||||
}
|
||||
value := configMap.Data["key-0"]
|
||||
if value != "val-0" {
|
||||
t.Fatal("got unexpected value", string(value))
|
||||
t.Fatal("got unexpected value", value)
|
||||
}
|
||||
|
||||
// Try to get a configmap that does not exist, and make sure we've got a "not found" error as a response.
|
||||
@@ -145,7 +145,7 @@ func TestListServices(t *testing.T) {
|
||||
// Create a pod lister that will list the pods defined above.
|
||||
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
|
||||
for _, service := range lsServices {
|
||||
indexer.Add(service)
|
||||
assert.NilError(t, indexer.Add(service))
|
||||
}
|
||||
serviceLister := corev1listers.NewServiceLister(indexer)
|
||||
|
||||
15
internal/podutils/README.md
Normal file
15
internal/podutils/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
Much of this is copied from k8s.io/kubernetes, even if it isn't a 1-1 copy of a
|
||||
file. This exists so we do not have to import from k8s.io/kubernetes which is
|
||||
currently problematic. Ideally most or all of this will go away and an upstream
|
||||
solution is found so that we can share an implementation with Kubelet without
|
||||
importing from k8s.io/kubernetes
|
||||
|
||||
|
||||
| filename | upstream location |
|
||||
|----------|-------------------|
|
||||
| envvars.go | https://github.com/kubernetes/kubernetes/blob/98d5dc5d36d34a7ee13368a7893dcb400ec4e566/pkg/kubelet/envvars/envvars.go#L32 |
|
||||
| helper.go#ConvertDownwardAPIFieldLabel | https://github.com/kubernetes/kubernetes/blob/98d5dc5d36d34a7ee13368a7893dcb400ec4e566/pkg/apis/core/pods/helpers.go#L65 |
|
||||
| helper.go#ExtractFieldPathAsString | https://github.com/kubernetes/kubernetes/blob/98d5dc5d36d34a7ee13368a7893dcb400ec4e566/pkg/fieldpath/fieldpath.go#L46 |
|
||||
| helper.go#SplitMaybeSubscriptedPath | https://github.com/kubernetes/kubernetes/blob/98d5dc5d36d34a7ee13368a7893dcb400ec4e566/pkg/fieldpath/fieldpath.go#L96 |
|
||||
| helper.go#FormatMap | https://github.com/kubernetes/kubernetes/blob/ea0764452222146c47ec826977f49d7001b0ea8c/pkg/fieldpath/fieldpath.go#L29 |
|
||||
| helper.go#IsServiceIPSet | https://github.com/kubernetes/kubernetes/blob/ea0764452222146c47ec826977f49d7001b0ea8c/pkg/apis/core/v1/helper/helpers.go#L139 |
|
||||
329
node/env.go → internal/podutils/env.go
Executable file → Normal file
329
node/env.go → internal/podutils/env.go
Executable file → Normal file
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package node
|
||||
package podutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -20,20 +20,16 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/internal/expansion"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/internal/manager"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
apivalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/client-go/tools/record"
|
||||
podshelper "k8s.io/kubernetes/pkg/apis/core/pods"
|
||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
fieldpath "k8s.io/kubernetes/pkg/fieldpath"
|
||||
"k8s.io/kubernetes/pkg/kubelet/envvars"
|
||||
"k8s.io/kubernetes/third_party/forked/golang/expansion"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -51,18 +47,18 @@ const (
|
||||
// ReasonFailedToReadOptionalSecret is the reason used in events emitted when an optional secret could not be read.
|
||||
ReasonFailedToReadOptionalSecret = "FailedToReadOptionalSecret"
|
||||
|
||||
// ReasonMandatoryConfigMapNotFound is the reason used in events emitted when an mandatory configmap is not found.
|
||||
// ReasonMandatoryConfigMapNotFound is the reason used in events emitted when a mandatory configmap is not found.
|
||||
ReasonMandatoryConfigMapNotFound = "MandatoryConfigMapNotFound"
|
||||
// ReasonMandatoryConfigMapKeyNotFound is the reason used in events emitted when an mandatory configmap key is not found.
|
||||
// ReasonMandatoryConfigMapKeyNotFound is the reason used in events emitted when a mandatory configmap key is not found.
|
||||
ReasonMandatoryConfigMapKeyNotFound = "MandatoryConfigMapKeyNotFound"
|
||||
// ReasonFailedToReadMandatoryConfigMap is the reason used in events emitted when an mandatory configmap could not be read.
|
||||
// ReasonFailedToReadMandatoryConfigMap is the reason used in events emitted when a mandatory configmap could not be read.
|
||||
ReasonFailedToReadMandatoryConfigMap = "FailedToReadMandatoryConfigMap"
|
||||
|
||||
// ReasonMandatorySecretNotFound is the reason used in events emitted when an mandatory secret is not found.
|
||||
// ReasonMandatorySecretNotFound is the reason used in events emitted when a mandatory secret is not found.
|
||||
ReasonMandatorySecretNotFound = "MandatorySecretNotFound"
|
||||
// ReasonMandatorySecretKeyNotFound is the reason used in events emitted when an mandatory secret key is not found.
|
||||
// ReasonMandatorySecretKeyNotFound is the reason used in events emitted when a mandatory secret key is not found.
|
||||
ReasonMandatorySecretKeyNotFound = "MandatorySecretKeyNotFound"
|
||||
// ReasonFailedToReadMandatorySecret is the reason used in events emitted when an mandatory secret could not be read.
|
||||
// ReasonFailedToReadMandatorySecret is the reason used in events emitted when a mandatory secret could not be read.
|
||||
ReasonFailedToReadMandatorySecret = "FailedToReadMandatorySecret"
|
||||
|
||||
// ReasonInvalidEnvironmentVariableNames is the reason used in events emitted when a configmap/secret referenced in a ".spec.containers[*].envFrom" field contains invalid environment variable names.
|
||||
@@ -71,9 +67,8 @@ const (
|
||||
|
||||
var masterServices = sets.NewString("kubernetes")
|
||||
|
||||
// populateEnvironmentVariables populates the environment of each container (and init container) in the specified pod.
|
||||
// TODO Make this the single exported function of a "pkg/environment" package in the future.
|
||||
func populateEnvironmentVariables(ctx context.Context, pod *corev1.Pod, rm *manager.ResourceManager, recorder record.EventRecorder) error {
|
||||
// PopulateEnvironmentVariables populates the environment of each container (and init container) in the specified pod.
|
||||
func PopulateEnvironmentVariables(ctx context.Context, pod *corev1.Pod, rm *manager.ResourceManager, recorder record.EventRecorder) error {
|
||||
|
||||
// Populate each init container's environment.
|
||||
for idx := range pod.Spec.InitContainers {
|
||||
@@ -109,7 +104,7 @@ func populateContainerEnvironment(ctx context.Context, pod *corev1.Pod, containe
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.13.1/pkg/kubelet/kubelet_pods.go#L557-L558
|
||||
container.EnvFrom = []corev1.EnvFromSource{}
|
||||
|
||||
res := make([]corev1.EnvVar, 0)
|
||||
res := make([]corev1.EnvVar, 0, len(tmpEnv))
|
||||
|
||||
for key, val := range tmpEnv {
|
||||
res = append(res, corev1.EnvVar{
|
||||
@@ -140,7 +135,7 @@ func getServiceEnvVarMap(rm *manager.ResourceManager, ns string, enableServiceLi
|
||||
for i := range services {
|
||||
service := services[i]
|
||||
// ignore services where ClusterIP is "None" or empty
|
||||
if !v1helper.IsServiceIPSet(service) {
|
||||
if !IsServiceIPSet(service) {
|
||||
continue
|
||||
}
|
||||
serviceName := service.Name
|
||||
@@ -163,7 +158,7 @@ func getServiceEnvVarMap(rm *manager.ResourceManager, ns string, enableServiceLi
|
||||
mappedServices = append(mappedServices, serviceMap[key])
|
||||
}
|
||||
|
||||
for _, e := range envvars.FromServices(mappedServices) {
|
||||
for _, e := range FromServices(mappedServices) {
|
||||
m[e.Name] = e.Value
|
||||
}
|
||||
return m, nil
|
||||
@@ -172,7 +167,7 @@ func getServiceEnvVarMap(rm *manager.ResourceManager, ns string, enableServiceLi
|
||||
// makeEnvironmentMapBasedOnEnvFrom returns a map representing the resolved environment of the specified container after being populated from the entries in the ".envFrom" field.
|
||||
func makeEnvironmentMapBasedOnEnvFrom(ctx context.Context, pod *corev1.Pod, container *corev1.Container, rm *manager.ResourceManager, recorder record.EventRecorder) (map[string]string, error) {
|
||||
// Create a map to hold the resulting environment.
|
||||
res := make(map[string]string, 0)
|
||||
res := make(map[string]string)
|
||||
// Iterate over "envFrom" references in order to populate the environment.
|
||||
loop:
|
||||
for _, envFrom := range container.EnvFrom {
|
||||
@@ -293,7 +288,6 @@ loop:
|
||||
|
||||
// makeEnvironmentMap returns a map representing the resolved environment of the specified container after being populated from the entries in the ".env" and ".envFrom" field.
|
||||
func makeEnvironmentMap(ctx context.Context, pod *corev1.Pod, container *corev1.Container, rm *manager.ResourceManager, recorder record.EventRecorder, res map[string]string) error {
|
||||
|
||||
// TODO If pod.Spec.EnableServiceLinks is nil then fail as per 1.14 kubelet.
|
||||
enableServiceLinks := corev1.DefaultEnableServiceLinks
|
||||
if pod.Spec.EnableServiceLinks != nil {
|
||||
@@ -315,136 +309,13 @@ func makeEnvironmentMap(ctx context.Context, pod *corev1.Pod, container *corev1.
|
||||
mappingFunc := expansion.MappingFuncFor(res, svcEnv)
|
||||
|
||||
// Iterate over environment variables in order to populate the map.
|
||||
loop:
|
||||
for _, env := range container.Env {
|
||||
switch {
|
||||
// Handle values that have been directly provided.
|
||||
case env.Value != "":
|
||||
// Expand variable references
|
||||
res[env.Name] = expansion.Expand(env.Value, mappingFunc)
|
||||
continue loop
|
||||
// Handle population from a configmap key.
|
||||
case env.ValueFrom != nil && env.ValueFrom.ConfigMapKeyRef != nil:
|
||||
// The environment variable must be set from a configmap.
|
||||
vf := env.ValueFrom.ConfigMapKeyRef
|
||||
// Check whether the key reference is optional.
|
||||
// This will control whether we fail when unable to read the requested key.
|
||||
optional := vf != nil && vf.Optional != nil && *vf.Optional
|
||||
// Try to grab the referenced configmap.
|
||||
m, err := rm.GetConfigMap(vf.Name, pod.Namespace)
|
||||
if err != nil {
|
||||
// We couldn't fetch the configmap.
|
||||
// However, if the key reference is optional we should not fail.
|
||||
if optional {
|
||||
if errors.IsNotFound(err) {
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalConfigMapNotFound, "skipping optional envvar %q: configmap %q not found", env.Name, vf.Name)
|
||||
} else {
|
||||
log.G(ctx).Warnf("failed to read configmap %q: %v", vf.Name, err)
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadOptionalConfigMap, "skipping optional envvar %q: failed to read configmap %q", env.Name, vf.Name)
|
||||
}
|
||||
// Continue on to the next reference.
|
||||
continue loop
|
||||
}
|
||||
// At this point we know the key reference is mandatory.
|
||||
// Hence, we should return a meaningful error.
|
||||
if errors.IsNotFound(err) {
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatoryConfigMapNotFound, "configmap %q not found", vf.Name)
|
||||
return fmt.Errorf("configmap %q not found", vf.Name)
|
||||
}
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadMandatoryConfigMap, "failed to read configmap %q", vf.Name)
|
||||
return fmt.Errorf("failed to read configmap %q: %v", vf.Name, err)
|
||||
}
|
||||
// At this point we have successfully fetched the target configmap.
|
||||
// We must now try to grab the requested key.
|
||||
var (
|
||||
keyExists bool
|
||||
keyValue string
|
||||
)
|
||||
if keyValue, keyExists = m.Data[vf.Key]; !keyExists {
|
||||
// The requested key does not exist.
|
||||
// However, we should not fail if the key reference is optional.
|
||||
if optional {
|
||||
// Continue on to the next reference.
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalConfigMapKeyNotFound, "skipping optional envvar %q: key %q does not exist in configmap %q", env.Name, vf.Key, vf.Name)
|
||||
continue loop
|
||||
}
|
||||
// At this point we know the key reference is mandatory.
|
||||
// Hence, we should fail.
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatoryConfigMapKeyNotFound, "key %q does not exist in configmap %q", vf.Key, vf.Name)
|
||||
return fmt.Errorf("configmap %q doesn't contain the %q key required by pod %s", vf.Name, vf.Key, pod.Name)
|
||||
}
|
||||
// Populate the environment variable and continue on to the next reference.
|
||||
res[env.Name] = keyValue
|
||||
continue loop
|
||||
// Handle population from a secret key.
|
||||
case env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil:
|
||||
vf := env.ValueFrom.SecretKeyRef
|
||||
// Check whether the key reference is optional.
|
||||
// This will control whether we fail when unable to read the requested key.
|
||||
optional := vf != nil && vf.Optional != nil && *vf.Optional
|
||||
// Try to grab the referenced secret.
|
||||
s, err := rm.GetSecret(vf.Name, pod.Namespace)
|
||||
if err != nil {
|
||||
// We couldn't fetch the secret.
|
||||
// However, if the key reference is optional we should not fail.
|
||||
if optional {
|
||||
if errors.IsNotFound(err) {
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalSecretNotFound, "skipping optional envvar %q: secret %q not found", env.Name, vf.Name)
|
||||
} else {
|
||||
log.G(ctx).Warnf("failed to read secret %q: %v", vf.Name, err)
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadOptionalSecret, "skipping optional envvar %q: failed to read secret %q", env.Name, vf.Name)
|
||||
}
|
||||
// Continue on to the next reference.
|
||||
continue loop
|
||||
}
|
||||
// At this point we know the key reference is mandatory.
|
||||
// Hence, we should return a meaningful error.
|
||||
if errors.IsNotFound(err) {
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatorySecretNotFound, "secret %q not found", vf.Name)
|
||||
return fmt.Errorf("secret %q not found", vf.Name)
|
||||
}
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadMandatorySecret, "failed to read secret %q", vf.Name)
|
||||
return fmt.Errorf("failed to read secret %q: %v", vf.Name, err)
|
||||
}
|
||||
// At this point we have successfully fetched the target secret.
|
||||
// We must now try to grab the requested key.
|
||||
var (
|
||||
keyExists bool
|
||||
keyValue []byte
|
||||
)
|
||||
if keyValue, keyExists = s.Data[vf.Key]; !keyExists {
|
||||
// The requested key does not exist.
|
||||
// However, we should not fail if the key reference is optional.
|
||||
if optional {
|
||||
// Continue on to the next reference.
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalSecretKeyNotFound, "skipping optional envvar %q: key %q does not exist in secret %q", env.Name, vf.Key, vf.Name)
|
||||
continue loop
|
||||
}
|
||||
// At this point we know the key reference is mandatory.
|
||||
// Hence, we should fail.
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatorySecretKeyNotFound, "key %q does not exist in secret %q", vf.Key, vf.Name)
|
||||
return fmt.Errorf("secret %q doesn't contain the %q key required by pod %s", vf.Name, vf.Key, pod.Name)
|
||||
}
|
||||
// Populate the environment variable and continue on to the next reference.
|
||||
res[env.Name] = string(keyValue)
|
||||
continue loop
|
||||
// Handle population from a field (downward API).
|
||||
case env.ValueFrom != nil && env.ValueFrom.FieldRef != nil:
|
||||
// https://github.com/virtual-kubelet/virtual-kubelet/issues/123
|
||||
vf := env.ValueFrom.FieldRef
|
||||
|
||||
runtimeVal, err := podFieldSelectorRuntimeValue(vf, pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res[env.Name] = runtimeVal
|
||||
|
||||
continue loop
|
||||
// Handle population from a resource request/limit.
|
||||
case env.ValueFrom != nil && env.ValueFrom.ResourceFieldRef != nil:
|
||||
// TODO Implement populating resource requests.
|
||||
continue loop
|
||||
val, err := getEnvironmentVariableValue(ctx, &env, mappingFunc, pod, container, rm, recorder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val != nil {
|
||||
res[env.Name] = *val
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,10 +329,160 @@ loop:
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEnvironmentVariableValue(ctx context.Context, env *corev1.EnvVar, mappingFunc func(string) string, pod *corev1.Pod, container *corev1.Container, rm *manager.ResourceManager, recorder record.EventRecorder) (*string, error) {
|
||||
if env.ValueFrom != nil {
|
||||
return getEnvironmentVariableValueWithValueFrom(ctx, env, mappingFunc, pod, container, rm, recorder)
|
||||
}
|
||||
// Handle values that have been directly provided after expanding variable references.
|
||||
return pointer.StringPtr(expansion.Expand(env.Value, mappingFunc)), nil
|
||||
}
|
||||
|
||||
func getEnvironmentVariableValueWithValueFrom(ctx context.Context, env *corev1.EnvVar, mappingFunc func(string) string, pod *corev1.Pod, container *corev1.Container, rm *manager.ResourceManager, recorder record.EventRecorder) (*string, error) {
|
||||
// Handle population from a configmap key.
|
||||
if env.ValueFrom.ConfigMapKeyRef != nil {
|
||||
return getEnvironmentVariableValueWithValueFromConfigMapKeyRef(ctx, env, mappingFunc, pod, container, rm, recorder)
|
||||
}
|
||||
|
||||
// Handle population from a secret key.
|
||||
if env.ValueFrom.SecretKeyRef != nil {
|
||||
return getEnvironmentVariableValueWithValueFromSecretKeyRef(ctx, env, mappingFunc, pod, container, rm, recorder)
|
||||
}
|
||||
|
||||
// Handle population from a field (downward API).
|
||||
if env.ValueFrom.FieldRef != nil {
|
||||
return getEnvironmentVariableValueWithValueFromFieldRef(ctx, env, mappingFunc, pod, container, rm, recorder)
|
||||
}
|
||||
if env.ValueFrom.ResourceFieldRef != nil {
|
||||
// TODO Implement populating resource requests.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
log.G(ctx).WithField("env", env).Error("Unhandled environment variable with non-nil env.ValueFrom, do not know how to populate")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getEnvironmentVariableValueWithValueFromConfigMapKeyRef(ctx context.Context, env *corev1.EnvVar, mappingFunc func(string) string, pod *corev1.Pod, container *corev1.Container, rm *manager.ResourceManager, recorder record.EventRecorder) (*string, error) {
|
||||
// The environment variable must be set from a configmap.
|
||||
vf := env.ValueFrom.ConfigMapKeyRef
|
||||
// Check whether the key reference is optional.
|
||||
// This will control whether we fail when unable to read the requested key.
|
||||
optional := vf != nil && vf.Optional != nil && *vf.Optional
|
||||
// Try to grab the referenced configmap.
|
||||
m, err := rm.GetConfigMap(vf.Name, pod.Namespace)
|
||||
if err != nil {
|
||||
// We couldn't fetch the configmap.
|
||||
// However, if the key reference is optional we should not fail.
|
||||
if optional {
|
||||
if errors.IsNotFound(err) {
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalConfigMapNotFound, "skipping optional envvar %q: configmap %q not found", env.Name, vf.Name)
|
||||
} else {
|
||||
log.G(ctx).Warnf("failed to read configmap %q: %v", vf.Name, err)
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadOptionalConfigMap, "skipping optional envvar %q: failed to read configmap %q", env.Name, vf.Name)
|
||||
}
|
||||
// Continue on to the next reference.
|
||||
return nil, nil
|
||||
}
|
||||
// At this point we know the key reference is mandatory.
|
||||
// Hence, we should return a meaningful error.
|
||||
if errors.IsNotFound(err) {
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatoryConfigMapNotFound, "configmap %q not found", vf.Name)
|
||||
return nil, fmt.Errorf("configmap %q not found", vf.Name)
|
||||
}
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadMandatoryConfigMap, "failed to read configmap %q", vf.Name)
|
||||
return nil, fmt.Errorf("failed to read configmap %q: %v", vf.Name, err)
|
||||
}
|
||||
// At this point we have successfully fetched the target configmap.
|
||||
// We must now try to grab the requested key.
|
||||
var (
|
||||
keyExists bool
|
||||
keyValue string
|
||||
)
|
||||
if keyValue, keyExists = m.Data[vf.Key]; !keyExists {
|
||||
// The requested key does not exist.
|
||||
// However, we should not fail if the key reference is optional.
|
||||
if optional {
|
||||
// Continue on to the next reference.
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalConfigMapKeyNotFound, "skipping optional envvar %q: key %q does not exist in configmap %q", env.Name, vf.Key, vf.Name)
|
||||
return nil, nil
|
||||
}
|
||||
// At this point we know the key reference is mandatory.
|
||||
// Hence, we should fail.
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatoryConfigMapKeyNotFound, "key %q does not exist in configmap %q", vf.Key, vf.Name)
|
||||
return nil, fmt.Errorf("configmap %q doesn't contain the %q key required by pod %s", vf.Name, vf.Key, pod.Name)
|
||||
}
|
||||
// Populate the environment variable and continue on to the next reference.
|
||||
return pointer.StringPtr(keyValue), nil
|
||||
}
|
||||
|
||||
func getEnvironmentVariableValueWithValueFromSecretKeyRef(ctx context.Context, env *corev1.EnvVar, mappingFunc func(string) string, pod *corev1.Pod, container *corev1.Container, rm *manager.ResourceManager, recorder record.EventRecorder) (*string, error) {
|
||||
vf := env.ValueFrom.SecretKeyRef
|
||||
// Check whether the key reference is optional.
|
||||
// This will control whether we fail when unable to read the requested key.
|
||||
optional := vf != nil && vf.Optional != nil && *vf.Optional
|
||||
// Try to grab the referenced secret.
|
||||
s, err := rm.GetSecret(vf.Name, pod.Namespace)
|
||||
if err != nil {
|
||||
// We couldn't fetch the secret.
|
||||
// However, if the key reference is optional we should not fail.
|
||||
if optional {
|
||||
if errors.IsNotFound(err) {
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalSecretNotFound, "skipping optional envvar %q: secret %q not found", env.Name, vf.Name)
|
||||
} else {
|
||||
log.G(ctx).Warnf("failed to read secret %q: %v", vf.Name, err)
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadOptionalSecret, "skipping optional envvar %q: failed to read secret %q", env.Name, vf.Name)
|
||||
}
|
||||
// Continue on to the next reference.
|
||||
return nil, nil
|
||||
}
|
||||
// At this point we know the key reference is mandatory.
|
||||
// Hence, we should return a meaningful error.
|
||||
if errors.IsNotFound(err) {
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatorySecretNotFound, "secret %q not found", vf.Name)
|
||||
return nil, fmt.Errorf("secret %q not found", vf.Name)
|
||||
}
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonFailedToReadMandatorySecret, "failed to read secret %q", vf.Name)
|
||||
return nil, fmt.Errorf("failed to read secret %q: %v", vf.Name, err)
|
||||
}
|
||||
// At this point we have successfully fetched the target secret.
|
||||
// We must now try to grab the requested key.
|
||||
var (
|
||||
keyExists bool
|
||||
keyValue []byte
|
||||
)
|
||||
if keyValue, keyExists = s.Data[vf.Key]; !keyExists {
|
||||
// The requested key does not exist.
|
||||
// However, we should not fail if the key reference is optional.
|
||||
if optional {
|
||||
// Continue on to the next reference.
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonOptionalSecretKeyNotFound, "skipping optional envvar %q: key %q does not exist in secret %q", env.Name, vf.Key, vf.Name)
|
||||
return nil, nil
|
||||
}
|
||||
// At this point we know the key reference is mandatory.
|
||||
// Hence, we should fail.
|
||||
recorder.Eventf(pod, corev1.EventTypeWarning, ReasonMandatorySecretKeyNotFound, "key %q does not exist in secret %q", vf.Key, vf.Name)
|
||||
return nil, fmt.Errorf("secret %q doesn't contain the %q key required by pod %s", vf.Name, vf.Key, pod.Name)
|
||||
}
|
||||
// Populate the environment variable and continue on to the next reference.
|
||||
return pointer.StringPtr(string(keyValue)), nil
|
||||
}
|
||||
|
||||
// Handle population from a field (downward API).
|
||||
func getEnvironmentVariableValueWithValueFromFieldRef(ctx context.Context, env *corev1.EnvVar, mappingFunc func(string) string, pod *corev1.Pod, container *corev1.Container, rm *manager.ResourceManager, recorder record.EventRecorder) (*string, error) {
|
||||
// https://github.com/virtual-kubelet/virtual-kubelet/issues/123
|
||||
vf := env.ValueFrom.FieldRef
|
||||
|
||||
runtimeVal, err := podFieldSelectorRuntimeValue(vf, pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pointer.StringPtr(runtimeVal), nil
|
||||
}
|
||||
|
||||
// podFieldSelectorRuntimeValue returns the runtime value of the given
|
||||
// selector for a pod.
|
||||
func podFieldSelectorRuntimeValue(fs *corev1.ObjectFieldSelector, pod *corev1.Pod) (string, error) {
|
||||
internalFieldPath, _, err := podshelper.ConvertDownwardAPIFieldLabel(fs.APIVersion, fs.FieldPath, "")
|
||||
internalFieldPath, _, err := ConvertDownwardAPIFieldLabel(fs.APIVersion, fs.FieldPath, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -472,5 +493,5 @@ func podFieldSelectorRuntimeValue(fs *corev1.ObjectFieldSelector, pod *corev1.Po
|
||||
return pod.Spec.ServiceAccountName, nil
|
||||
|
||||
}
|
||||
return fieldpath.ExtractFieldPathAsString(pod, internalFieldPath)
|
||||
return ExtractFieldPathAsString(pod, internalFieldPath)
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package node
|
||||
package podutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -215,7 +215,7 @@ func TestPopulatePodWithInitContainersUsingEnv(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate the pod's environment.
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.Check(t, err)
|
||||
|
||||
// Make sure that all the containers' environments contain all the expected keys and values.
|
||||
@@ -375,7 +375,7 @@ func TestPopulatePodWithInitContainersUsingEnvWithFieldRef(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate the pod's environment.
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Make sure that all the containers' environments contain all the expected keys and values.
|
||||
@@ -491,7 +491,7 @@ func TestPopulatePodWithInitContainersUsingEnvFrom(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate the pod's environment.
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.Check(t, err)
|
||||
|
||||
// Make sure that all the containers' environments contain all the expected keys and values.
|
||||
@@ -632,7 +632,7 @@ func TestEnvFromConfigMapAndSecretWithInvalidKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate the pods's environment.
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.Check(t, err)
|
||||
|
||||
// Make sure that the container's environment has two variables (corresponding to the single valid key in both the configmap and the secret).
|
||||
@@ -663,7 +663,7 @@ func TestEnvFromConfigMapAndSecretWithInvalidKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestEnvOverridesEnvFrom populates the environment of a container from a configmap, and from another configmap's key with a "conflicting" key.
|
||||
// Then, it checks that the value of the "conflicting" key has been correctly overriden.
|
||||
// Then, it checks that the value of the "conflicting" key has been correctly overridden.
|
||||
func TestEnvOverridesEnvFrom(t *testing.T) {
|
||||
rm := testutil.FakeResourceManager(configMap3)
|
||||
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
|
||||
@@ -672,7 +672,7 @@ func TestEnvOverridesEnvFrom(t *testing.T) {
|
||||
override := "__override__"
|
||||
|
||||
// Create a pod object having a single container.
|
||||
// The container's environment is to be populated from a configmap, and later overriden with a value provided directly.
|
||||
// The container's environment is to be populated from a configmap, and later overridden with a value provided directly.
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
@@ -703,7 +703,7 @@ func TestEnvOverridesEnvFrom(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate the pods's environment.
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.Check(t, err)
|
||||
|
||||
// Make sure that the container's environment contains all the expected keys and values.
|
||||
@@ -780,7 +780,7 @@ func TestEnvFromInexistentConfigMaps(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate the pods's environment.
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
|
||||
// Make sure that two events have been recorded with the correct reason and message.
|
||||
@@ -837,7 +837,7 @@ func TestEnvFromInexistentSecrets(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate the pods's environment.
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
|
||||
// Make sure that two events have been recorded with the correct reason and message.
|
||||
@@ -877,7 +877,7 @@ func TestEnvReferencingInexistentConfigMapKey(t *testing.T) {
|
||||
},
|
||||
Key: "key",
|
||||
// This scenario has been observed before https://github.com/virtual-kubelet/virtual-kubelet/issues/444#issuecomment-449611851.
|
||||
// A nil value of optional means "mandatory", hence we should expect "populateEnvironmentVariables" to return an error.
|
||||
// A nil value of optional means "mandatory", hence we should expect "PopulateEnvironmentVariables" to return an error.
|
||||
Optional: nil,
|
||||
},
|
||||
},
|
||||
@@ -890,7 +890,7 @@ func TestEnvReferencingInexistentConfigMapKey(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate the pods's environment.
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
|
||||
// Make sure that two events have been recorded with the correct reason and message.
|
||||
@@ -927,7 +927,7 @@ func TestEnvReferencingInexistentSecretKey(t *testing.T) {
|
||||
},
|
||||
Key: "key",
|
||||
// This scenario has been observed before https://github.com/virtual-kubelet/virtual-kubelet/issues/444#issuecomment-449611851.
|
||||
// A nil value of optional means "mandatory", hence we should expect "populateEnvironmentVariables" to return an error.
|
||||
// A nil value of optional means "mandatory", hence we should expect "PopulateEnvironmentVariables" to return an error.
|
||||
Optional: nil,
|
||||
},
|
||||
},
|
||||
@@ -940,7 +940,7 @@ func TestEnvReferencingInexistentSecretKey(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate the pods's environment.
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
|
||||
// Make sure that two events have been recorded with the correct reason and message.
|
||||
@@ -1023,7 +1023,7 @@ func TestServiceEnvVar(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
pod.Spec.EnableServiceLinks = tc.enableServiceLinks
|
||||
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.NilError(t, err, "[%s]", tc.name)
|
||||
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, tc.expectedEnvs, sortOpt))
|
||||
}
|
||||
@@ -1066,7 +1066,7 @@ func TestComposingEnv(t *testing.T) {
|
||||
}
|
||||
|
||||
// Populate the pods's environment.
|
||||
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.Check(t, err)
|
||||
|
||||
// Make sure that the container's environment contains all the expected keys and values.
|
||||
@@ -1090,3 +1090,45 @@ func TestComposingEnv(t *testing.T) {
|
||||
// Make sure that no events have been recorded.
|
||||
assert.Check(t, is.Len(er.Events, 0))
|
||||
}
|
||||
|
||||
// TestEmptyEnvVar tests that env var can be have the value ""
|
||||
func TestEmptyEnvVar(t *testing.T) {
|
||||
rm := testutil.FakeResourceManager()
|
||||
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
|
||||
|
||||
// Create a pod object having a single container.
|
||||
// The container's third environment variable is composed of the previous two.
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: "pod-0",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: envVarName1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Populate the pods's environment.
|
||||
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||
assert.Check(t, err)
|
||||
|
||||
// Make sure that the container's environment contains all the expected keys and values.
|
||||
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, []corev1.EnvVar{
|
||||
{
|
||||
Name: envVarName1,
|
||||
},
|
||||
},
|
||||
sortOpt,
|
||||
))
|
||||
|
||||
// Make sure that no events have been recorded.
|
||||
assert.Check(t, is.Len(er.Events, 0))
|
||||
}
|
||||
112
internal/podutils/envvars.go
Normal file
112
internal/podutils/envvars.go
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
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 podutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// FromServices builds environment variables that a container is started with,
|
||||
// which tell the container where to find the services it may need, which are
|
||||
// provided as an argument.
|
||||
func FromServices(services []*v1.Service) []v1.EnvVar {
|
||||
var result []v1.EnvVar
|
||||
for i := range services {
|
||||
service := services[i]
|
||||
|
||||
// ignore services where ClusterIP is "None" or empty
|
||||
// the services passed to this method should be pre-filtered
|
||||
// only services that have the cluster IP set should be included here
|
||||
if !IsServiceIPSet(service) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Host
|
||||
name := makeEnvVariableName(service.Name) + "_SERVICE_HOST"
|
||||
result = append(result, v1.EnvVar{Name: name, Value: service.Spec.ClusterIP})
|
||||
// First port - give it the backwards-compatible name
|
||||
name = makeEnvVariableName(service.Name) + "_SERVICE_PORT"
|
||||
result = append(result, v1.EnvVar{Name: name, Value: strconv.Itoa(int(service.Spec.Ports[0].Port))})
|
||||
// All named ports (only the first may be unnamed, checked in validation)
|
||||
for i := range service.Spec.Ports {
|
||||
sp := &service.Spec.Ports[i]
|
||||
if sp.Name != "" {
|
||||
pn := name + "_" + makeEnvVariableName(sp.Name)
|
||||
result = append(result, v1.EnvVar{Name: pn, Value: strconv.Itoa(int(sp.Port))})
|
||||
}
|
||||
}
|
||||
// Docker-compatible vars.
|
||||
result = append(result, makeLinkVariables(service)...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func makeEnvVariableName(str string) string {
|
||||
// TODO: If we simplify to "all names are DNS1123Subdomains" this
|
||||
// will need two tweaks:
|
||||
// 1) Handle leading digits
|
||||
// 2) Handle dots
|
||||
return strings.ToUpper(strings.Replace(str, "-", "_", -1))
|
||||
}
|
||||
|
||||
func makeLinkVariables(service *v1.Service) []v1.EnvVar {
|
||||
prefix := makeEnvVariableName(service.Name)
|
||||
all := []v1.EnvVar{}
|
||||
for i := range service.Spec.Ports {
|
||||
sp := &service.Spec.Ports[i]
|
||||
|
||||
protocol := string(v1.ProtocolTCP)
|
||||
if sp.Protocol != "" {
|
||||
protocol = string(sp.Protocol)
|
||||
}
|
||||
|
||||
hostPort := net.JoinHostPort(service.Spec.ClusterIP, strconv.Itoa(int(sp.Port)))
|
||||
|
||||
if i == 0 {
|
||||
// Docker special-cases the first port.
|
||||
all = append(all, v1.EnvVar{
|
||||
Name: prefix + "_PORT",
|
||||
Value: fmt.Sprintf("%s://%s", strings.ToLower(protocol), hostPort),
|
||||
})
|
||||
}
|
||||
portPrefix := fmt.Sprintf("%s_PORT_%d_%s", prefix, sp.Port, strings.ToUpper(protocol))
|
||||
all = append(all, []v1.EnvVar{
|
||||
{
|
||||
Name: portPrefix,
|
||||
Value: fmt.Sprintf("%s://%s", strings.ToLower(protocol), hostPort),
|
||||
},
|
||||
{
|
||||
Name: portPrefix + "_PROTO",
|
||||
Value: strings.ToLower(protocol),
|
||||
},
|
||||
{
|
||||
Name: portPrefix + "_PORT",
|
||||
Value: strconv.Itoa(int(sp.Port)),
|
||||
},
|
||||
{
|
||||
Name: portPrefix + "_ADDR",
|
||||
Value: service.Spec.ClusterIP,
|
||||
},
|
||||
}...)
|
||||
}
|
||||
return all
|
||||
}
|
||||
156
internal/podutils/helper.go
Normal file
156
internal/podutils/helper.go
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
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 podutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// ConvertDownwardAPIFieldLabel converts the specified downward API field label
|
||||
// and its value in the pod of the specified version to the internal version,
|
||||
// and returns the converted label and value. This function returns an error if
|
||||
// the conversion fails.
|
||||
func ConvertDownwardAPIFieldLabel(version, label, value string) (string, string, error) {
|
||||
if version != "v1" {
|
||||
return "", "", fmt.Errorf("unsupported pod version: %s", version)
|
||||
}
|
||||
|
||||
if path, _, ok := SplitMaybeSubscriptedPath(label); ok {
|
||||
switch path {
|
||||
case "metadata.annotations", "metadata.labels":
|
||||
return label, value, nil
|
||||
default:
|
||||
return "", "", fmt.Errorf("field label does not support subscript: %s", label)
|
||||
}
|
||||
}
|
||||
|
||||
switch label {
|
||||
case "metadata.annotations",
|
||||
"metadata.labels",
|
||||
"metadata.name",
|
||||
"metadata.namespace",
|
||||
"metadata.uid",
|
||||
"spec.nodeName",
|
||||
"spec.restartPolicy",
|
||||
"spec.serviceAccountName",
|
||||
"spec.schedulerName",
|
||||
"status.phase",
|
||||
"status.hostIP",
|
||||
"status.podIP",
|
||||
"status.podIPs":
|
||||
return label, value, nil
|
||||
// This is for backwards compatibility with old v1 clients which send spec.host
|
||||
case "spec.host":
|
||||
return "spec.nodeName", value, nil
|
||||
default:
|
||||
return "", "", fmt.Errorf("field label not supported: %s", label)
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractFieldPathAsString extracts the field from the given object
|
||||
// and returns it as a string. The object must be a pointer to an
|
||||
// API type.
|
||||
func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if path, subscript, ok := SplitMaybeSubscriptedPath(fieldPath); ok {
|
||||
switch path {
|
||||
case "metadata.annotations":
|
||||
if errs := validation.IsQualifiedName(strings.ToLower(subscript)); len(errs) != 0 {
|
||||
return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";"))
|
||||
}
|
||||
return accessor.GetAnnotations()[subscript], nil
|
||||
case "metadata.labels":
|
||||
if errs := validation.IsQualifiedName(subscript); len(errs) != 0 {
|
||||
return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";"))
|
||||
}
|
||||
return accessor.GetLabels()[subscript], nil
|
||||
default:
|
||||
return "", fmt.Errorf("fieldPath %q does not support subscript", fieldPath)
|
||||
}
|
||||
}
|
||||
|
||||
switch fieldPath {
|
||||
case "metadata.annotations":
|
||||
return FormatMap(accessor.GetAnnotations()), nil
|
||||
case "metadata.labels":
|
||||
return FormatMap(accessor.GetLabels()), nil
|
||||
case "metadata.name":
|
||||
return accessor.GetName(), nil
|
||||
case "metadata.namespace":
|
||||
return accessor.GetNamespace(), nil
|
||||
case "metadata.uid":
|
||||
return string(accessor.GetUID()), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath)
|
||||
}
|
||||
|
||||
// SplitMaybeSubscriptedPath checks whether the specified fieldPath is
|
||||
// subscripted, and
|
||||
// - if yes, this function splits the fieldPath into path and subscript, and
|
||||
// returns (path, subscript, true).
|
||||
// - if no, this function returns (fieldPath, "", false).
|
||||
//
|
||||
// Example inputs and outputs:
|
||||
// - "metadata.annotations['myKey']" --> ("metadata.annotations", "myKey", true)
|
||||
// - "metadata.annotations['a[b]c']" --> ("metadata.annotations", "a[b]c", true)
|
||||
// - "metadata.labels['']" --> ("metadata.labels", "", true)
|
||||
// - "metadata.labels" --> ("metadata.labels", "", false)
|
||||
func SplitMaybeSubscriptedPath(fieldPath string) (string, string, bool) {
|
||||
if !strings.HasSuffix(fieldPath, "']") {
|
||||
return fieldPath, "", false
|
||||
}
|
||||
s := strings.TrimSuffix(fieldPath, "']")
|
||||
parts := strings.SplitN(s, "['", 2)
|
||||
if len(parts) < 2 {
|
||||
return fieldPath, "", false
|
||||
}
|
||||
if len(parts[0]) == 0 {
|
||||
return fieldPath, "", false
|
||||
}
|
||||
return parts[0], parts[1], true
|
||||
}
|
||||
|
||||
// FormatMap formats map[string]string to a string.
|
||||
func FormatMap(m map[string]string) (fmtStr string) {
|
||||
// output with keys in sorted order to provide stable output
|
||||
keys := sets.NewString()
|
||||
for key := range m {
|
||||
keys.Insert(key)
|
||||
}
|
||||
for _, key := range keys.List() {
|
||||
fmtStr += fmt.Sprintf("%v=%q\n", key, m[key])
|
||||
}
|
||||
fmtStr = strings.TrimSuffix(fmtStr, "\n")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsServiceIPSet aims to check if the service's ClusterIP is set or not the objective is not to perform validation here
|
||||
func IsServiceIPSet(service *corev1.Service) bool {
|
||||
return service.Spec.ClusterIP != corev1.ClusterIPNone && service.Spec.ClusterIP != ""
|
||||
}
|
||||
502
internal/queue/queue.go
Normal file
502
internal/queue/queue.go
Normal file
@@ -0,0 +1,502 @@
|
||||
// Copyright © 2017 The virtual-kubelet authors
|
||||
//
|
||||
// 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 queue
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
pkgerrors "github.com/pkg/errors"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/trace"
|
||||
"golang.org/x/sync/semaphore"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxRetries is the number of times we try to process a given key before permanently forgetting it.
|
||||
MaxRetries = 20
|
||||
)
|
||||
|
||||
// ShouldRetryFunc is a mechanism to have a custom retry policy
|
||||
type ShouldRetryFunc func(ctx context.Context, key string, timesTried int, originallyAdded time.Time, err error) (*time.Duration, error)
|
||||
|
||||
// ItemHandler is a callback that handles a single key on the Queue
|
||||
type ItemHandler func(ctx context.Context, key string) error
|
||||
|
||||
// Queue implements a wrapper around workqueue with native VK instrumentation
|
||||
type Queue struct {
|
||||
// clock is used for testing
|
||||
clock clock.Clock
|
||||
// lock protects running, and the items list / map
|
||||
lock sync.Mutex
|
||||
running bool
|
||||
name string
|
||||
handler ItemHandler
|
||||
|
||||
ratelimiter workqueue.RateLimiter
|
||||
// items are items that are marked dirty waiting for processing.
|
||||
items *list.List
|
||||
// itemInQueue is a map of (string) key -> item while it is in the items list
|
||||
itemsInQueue map[string]*list.Element
|
||||
// itemsBeingProcessed is a map of (string) key -> item once it has been moved
|
||||
itemsBeingProcessed map[string]*queueItem
|
||||
// Wait for next semaphore is an exclusive (1 item) lock that is taken every time items is checked to see if there
|
||||
// is an item in queue for work
|
||||
waitForNextItemSemaphore *semaphore.Weighted
|
||||
|
||||
// wakeup
|
||||
wakeupCh chan struct{}
|
||||
|
||||
retryFunc ShouldRetryFunc
|
||||
}
|
||||
|
||||
type queueItem struct {
|
||||
key string
|
||||
plannedToStartWorkAt time.Time
|
||||
redirtiedAt time.Time
|
||||
redirtiedWithRatelimit bool
|
||||
forget bool
|
||||
requeues int
|
||||
|
||||
// Debugging information only
|
||||
originallyAdded time.Time
|
||||
addedViaRedirty bool
|
||||
delayedViaRateLimit *time.Duration
|
||||
}
|
||||
|
||||
func (item *queueItem) String() string {
|
||||
return fmt.Sprintf("<plannedToStartWorkAt:%s key: %s>", item.plannedToStartWorkAt.String(), item.key)
|
||||
}
|
||||
|
||||
// New creates a queue
|
||||
//
|
||||
// It expects to get a item rate limiter, and a friendly name which is used in logs, and in the internal kubernetes
|
||||
// metrics. If retryFunc is nil, the default retry function.
|
||||
func New(ratelimiter workqueue.RateLimiter, name string, handler ItemHandler, retryFunc ShouldRetryFunc) *Queue {
|
||||
if retryFunc == nil {
|
||||
retryFunc = DefaultRetryFunc
|
||||
}
|
||||
return &Queue{
|
||||
clock: clock.RealClock{},
|
||||
name: name,
|
||||
ratelimiter: ratelimiter,
|
||||
items: list.New(),
|
||||
itemsBeingProcessed: make(map[string]*queueItem),
|
||||
itemsInQueue: make(map[string]*list.Element),
|
||||
handler: handler,
|
||||
wakeupCh: make(chan struct{}, 1),
|
||||
waitForNextItemSemaphore: semaphore.NewWeighted(1),
|
||||
retryFunc: retryFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue enqueues the key in a rate limited fashion
|
||||
func (q *Queue) Enqueue(ctx context.Context, key string) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
q.insert(ctx, key, true, nil)
|
||||
}
|
||||
|
||||
// EnqueueWithoutRateLimit enqueues the key without a rate limit
|
||||
func (q *Queue) EnqueueWithoutRateLimit(ctx context.Context, key string) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
q.insert(ctx, key, false, nil)
|
||||
}
|
||||
|
||||
// Forget forgets the key
|
||||
func (q *Queue) Forget(ctx context.Context, key string) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
ctx, span := trace.StartSpan(ctx, "Forget")
|
||||
defer span.End()
|
||||
|
||||
ctx = span.WithFields(ctx, map[string]interface{}{
|
||||
"queue": q.name,
|
||||
"key": key,
|
||||
})
|
||||
|
||||
if item, ok := q.itemsInQueue[key]; ok {
|
||||
span.WithField(ctx, "status", "itemInQueue")
|
||||
delete(q.itemsInQueue, key)
|
||||
q.items.Remove(item)
|
||||
return
|
||||
}
|
||||
|
||||
if qi, ok := q.itemsBeingProcessed[key]; ok {
|
||||
span.WithField(ctx, "status", "itemBeingProcessed")
|
||||
qi.forget = true
|
||||
return
|
||||
}
|
||||
span.WithField(ctx, "status", "notfound")
|
||||
}
|
||||
|
||||
func durationDeref(duration *time.Duration, def time.Duration) time.Duration {
|
||||
if duration == nil {
|
||||
return def
|
||||
}
|
||||
|
||||
return *duration
|
||||
}
|
||||
|
||||
// insert inserts a new item to be processed at time time. It will not further delay items if when is later than the
|
||||
// original time the item was scheduled to be processed. If when is earlier, it will "bring it forward"
|
||||
// If ratelimit is specified, and delay is nil, then the ratelimiter's delay (return from When function) will be used
|
||||
// If ratelimit is specified, and the delay is non-nil, then the delay value will be used
|
||||
// If ratelimit is false, then only delay is used to schedule the work. If delay is nil, it will be considered 0.
|
||||
func (q *Queue) insert(ctx context.Context, key string, ratelimit bool, delay *time.Duration) *queueItem {
|
||||
ctx, span := trace.StartSpan(ctx, "insert")
|
||||
defer span.End()
|
||||
|
||||
ctx = span.WithFields(ctx, map[string]interface{}{
|
||||
"queue": q.name,
|
||||
"key": key,
|
||||
"ratelimit": ratelimit,
|
||||
})
|
||||
if delay == nil {
|
||||
ctx = span.WithField(ctx, "delay", "nil")
|
||||
} else {
|
||||
ctx = span.WithField(ctx, "delay", delay.String())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
select {
|
||||
case q.wakeupCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
|
||||
// First see if the item is already being processed
|
||||
if item, ok := q.itemsBeingProcessed[key]; ok {
|
||||
span.WithField(ctx, "status", "itemsBeingProcessed")
|
||||
when := q.clock.Now().Add(durationDeref(delay, 0))
|
||||
// Is the item already been redirtied?
|
||||
if item.redirtiedAt.IsZero() {
|
||||
item.redirtiedAt = when
|
||||
item.redirtiedWithRatelimit = ratelimit
|
||||
} else if when.Before(item.redirtiedAt) {
|
||||
item.redirtiedAt = when
|
||||
item.redirtiedWithRatelimit = ratelimit
|
||||
}
|
||||
item.forget = false
|
||||
return item
|
||||
}
|
||||
|
||||
// Is the item already in the queue?
|
||||
if item, ok := q.itemsInQueue[key]; ok {
|
||||
span.WithField(ctx, "status", "itemsInQueue")
|
||||
qi := item.Value.(*queueItem)
|
||||
when := q.clock.Now().Add(durationDeref(delay, 0))
|
||||
q.adjustPosition(qi, item, when)
|
||||
return qi
|
||||
}
|
||||
|
||||
span.WithField(ctx, "status", "added")
|
||||
now := q.clock.Now()
|
||||
val := &queueItem{
|
||||
key: key,
|
||||
plannedToStartWorkAt: now,
|
||||
originallyAdded: now,
|
||||
}
|
||||
|
||||
if ratelimit {
|
||||
actualDelay := q.ratelimiter.When(key)
|
||||
// Check if delay is overridden
|
||||
if delay != nil {
|
||||
actualDelay = *delay
|
||||
}
|
||||
span.WithField(ctx, "delay", actualDelay.String())
|
||||
val.plannedToStartWorkAt = val.plannedToStartWorkAt.Add(actualDelay)
|
||||
val.delayedViaRateLimit = &actualDelay
|
||||
} else {
|
||||
val.plannedToStartWorkAt = val.plannedToStartWorkAt.Add(durationDeref(delay, 0))
|
||||
}
|
||||
|
||||
for item := q.items.Back(); item != nil; item = item.Prev() {
|
||||
qi := item.Value.(*queueItem)
|
||||
if qi.plannedToStartWorkAt.Before(val.plannedToStartWorkAt) {
|
||||
q.itemsInQueue[key] = q.items.InsertAfter(val, item)
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
q.itemsInQueue[key] = q.items.PushFront(val)
|
||||
return val
|
||||
}
|
||||
|
||||
func (q *Queue) adjustPosition(qi *queueItem, element *list.Element, when time.Time) {
|
||||
if when.After(qi.plannedToStartWorkAt) {
|
||||
// The item has already been delayed appropriately
|
||||
return
|
||||
}
|
||||
|
||||
qi.plannedToStartWorkAt = when
|
||||
for prev := element.Prev(); prev != nil; prev = prev.Prev() {
|
||||
item := prev.Value.(*queueItem)
|
||||
// does this item plan to start work *before* the new time? If so add it
|
||||
if item.plannedToStartWorkAt.Before(when) {
|
||||
q.items.MoveAfter(element, prev)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
q.items.MoveToFront(element)
|
||||
}
|
||||
|
||||
// EnqueueWithoutRateLimitWithDelay enqueues without rate limiting, but work will not start for this given delay period
|
||||
func (q *Queue) EnqueueWithoutRateLimitWithDelay(ctx context.Context, key string, after time.Duration) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
q.insert(ctx, key, false, &after)
|
||||
}
|
||||
|
||||
// Empty returns if the queue has no items in it
|
||||
//
|
||||
// It should only be used for debugging.
|
||||
func (q *Queue) Empty() bool {
|
||||
return q.Len() == 0
|
||||
}
|
||||
|
||||
// Len includes items that are in the queue, and are being processed
|
||||
func (q *Queue) Len() int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
if q.items.Len() != len(q.itemsInQueue) {
|
||||
panic("Internally inconsistent state")
|
||||
}
|
||||
|
||||
return q.items.Len() + len(q.itemsBeingProcessed)
|
||||
}
|
||||
|
||||
// Run starts the workers
|
||||
//
|
||||
// It blocks until context is cancelled, and all of the workers exit.
|
||||
func (q *Queue) Run(ctx context.Context, workers int) {
|
||||
if workers <= 0 {
|
||||
panic(fmt.Sprintf("Workers must be greater than 0, got: %d", workers))
|
||||
}
|
||||
|
||||
q.lock.Lock()
|
||||
if q.running {
|
||||
panic(fmt.Sprintf("Queue %s is already running", q.name))
|
||||
}
|
||||
q.running = true
|
||||
q.lock.Unlock()
|
||||
defer func() {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
q.running = false
|
||||
}()
|
||||
|
||||
// Make sure all workers are stopped before we finish up.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
group := &wait.Group{}
|
||||
for i := 0; i < workers; i++ {
|
||||
// This is required because i is referencing a mutable variable and that's running in a separate goroutine
|
||||
idx := i
|
||||
group.StartWithContext(ctx, func(ctx context.Context) {
|
||||
q.worker(ctx, idx)
|
||||
})
|
||||
}
|
||||
defer group.Wait()
|
||||
<-ctx.Done()
|
||||
}
|
||||
|
||||
func (q *Queue) worker(ctx context.Context, i int) {
|
||||
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(map[string]interface{}{
|
||||
"workerId": i,
|
||||
"queue": q.name,
|
||||
}))
|
||||
for q.handleQueueItem(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Queue) getNextItem(ctx context.Context) (*queueItem, error) {
|
||||
if err := q.waitForNextItemSemaphore.Acquire(ctx, 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer q.waitForNextItemSemaphore.Release(1)
|
||||
|
||||
for {
|
||||
q.lock.Lock()
|
||||
element := q.items.Front()
|
||||
if element == nil {
|
||||
// Wait for the next item
|
||||
q.lock.Unlock()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-q.wakeupCh:
|
||||
}
|
||||
} else {
|
||||
qi := element.Value.(*queueItem)
|
||||
timeUntilProcessing := time.Until(qi.plannedToStartWorkAt)
|
||||
|
||||
// Do we need to sleep? If not, let's party.
|
||||
if timeUntilProcessing <= 0 {
|
||||
q.itemsBeingProcessed[qi.key] = qi
|
||||
q.items.Remove(element)
|
||||
delete(q.itemsInQueue, qi.key)
|
||||
q.lock.Unlock()
|
||||
return qi, nil
|
||||
}
|
||||
|
||||
q.lock.Unlock()
|
||||
if err := func() error {
|
||||
timer := q.clock.NewTimer(timeUntilProcessing)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-timer.C():
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-q.wakeupCh:
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleQueueItem handles a single item
|
||||
//
|
||||
// A return value of "false" indicates that further processing should be stopped.
|
||||
func (q *Queue) handleQueueItem(ctx context.Context) bool {
|
||||
ctx, span := trace.StartSpan(ctx, "handleQueueItem")
|
||||
defer span.End()
|
||||
|
||||
qi, err := q.getNextItem(ctx)
|
||||
if err != nil {
|
||||
span.SetStatus(err)
|
||||
return false
|
||||
}
|
||||
|
||||
// We expect strings to come off the work Queue.
|
||||
// These are of the form namespace/name.
|
||||
// We do this as the delayed nature of the work Queue means the items in the informer cache may actually be more u
|
||||
// to date that when the item was initially put onto the workqueue.
|
||||
ctx = span.WithField(ctx, "key", qi.key)
|
||||
log.G(ctx).Debug("Got Queue object")
|
||||
|
||||
err = q.handleQueueItemObject(ctx, qi)
|
||||
if err != nil {
|
||||
// We've actually hit an error, so we set the span's status based on the error.
|
||||
span.SetStatus(err)
|
||||
log.G(ctx).WithError(err).Error("Error processing Queue item")
|
||||
return true
|
||||
}
|
||||
log.G(ctx).Debug("Processed Queue item")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (q *Queue) handleQueueItemObject(ctx context.Context, qi *queueItem) error {
|
||||
// This is a separate function / span, because the handleQueueItem span is the time spent waiting for the object
|
||||
// plus the time spend handling the object. Instead, this function / span is scoped to a single object.
|
||||
ctx, span := trace.StartSpan(ctx, "handleQueueItemObject")
|
||||
defer span.End()
|
||||
|
||||
ctx = span.WithFields(ctx, map[string]interface{}{
|
||||
"requeues": qi.requeues,
|
||||
"originallyAdded": qi.originallyAdded.String(),
|
||||
"addedViaRedirty": qi.addedViaRedirty,
|
||||
"plannedForWork": qi.plannedToStartWorkAt.String(),
|
||||
})
|
||||
|
||||
if qi.delayedViaRateLimit != nil {
|
||||
ctx = span.WithField(ctx, "delayedViaRateLimit", qi.delayedViaRateLimit.String())
|
||||
}
|
||||
|
||||
// Add the current key as an attribute to the current span.
|
||||
ctx = span.WithField(ctx, "key", qi.key)
|
||||
// Run the syncHandler, passing it the namespace/name string of the Pod resource to be synced.
|
||||
err := q.handler(ctx, qi.key)
|
||||
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
delete(q.itemsBeingProcessed, qi.key)
|
||||
if qi.forget {
|
||||
q.ratelimiter.Forget(qi.key)
|
||||
log.G(ctx).WithError(err).Warnf("forgetting %q as told to forget while in progress", qi.key)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ctx = span.WithField(ctx, "error", err.Error())
|
||||
var delay *time.Duration
|
||||
|
||||
// Stash the original error for logging below
|
||||
originalError := err
|
||||
delay, err = q.retryFunc(ctx, qi.key, qi.requeues+1, qi.originallyAdded, err)
|
||||
if err == nil {
|
||||
// Put the item back on the work Queue to handle any transient errors.
|
||||
log.G(ctx).WithError(originalError).Warnf("requeuing %q due to failed sync", qi.key)
|
||||
newQI := q.insert(ctx, qi.key, true, delay)
|
||||
newQI.requeues = qi.requeues + 1
|
||||
newQI.originallyAdded = qi.originallyAdded
|
||||
|
||||
return nil
|
||||
}
|
||||
if !qi.redirtiedAt.IsZero() {
|
||||
err = fmt.Errorf("temporarily (requeued) forgetting %q due to: %w", qi.key, err)
|
||||
} else {
|
||||
err = fmt.Errorf("forgetting %q due to: %w", qi.key, err)
|
||||
}
|
||||
}
|
||||
|
||||
// We've exceeded the maximum retries or we were successful.
|
||||
q.ratelimiter.Forget(qi.key)
|
||||
if !qi.redirtiedAt.IsZero() {
|
||||
delay := time.Until(qi.redirtiedAt)
|
||||
newQI := q.insert(ctx, qi.key, qi.redirtiedWithRatelimit, &delay)
|
||||
newQI.addedViaRedirty = true
|
||||
}
|
||||
|
||||
span.SetStatus(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *Queue) String() string {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
items := make([]string, 0, q.items.Len())
|
||||
|
||||
for next := q.items.Front(); next != nil; next = next.Next() {
|
||||
items = append(items, next.Value.(*queueItem).String())
|
||||
}
|
||||
return fmt.Sprintf("<items:%s>", items)
|
||||
}
|
||||
|
||||
// DefaultRetryFunc is the default function used for retries by the queue subsystem.
|
||||
func DefaultRetryFunc(ctx context.Context, key string, timesTried int, originallyAdded time.Time, err error) (*time.Duration, error) {
|
||||
if timesTried < MaxRetries {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, pkgerrors.Wrapf(err, "maximum retries (%d) reached", MaxRetries)
|
||||
}
|
||||
510
internal/queue/queue_test.go
Normal file
510
internal/queue/queue_test.go
Normal file
@@ -0,0 +1,510 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||
logruslogger "github.com/virtual-kubelet/virtual-kubelet/log/logrus"
|
||||
"golang.org/x/time/rate"
|
||||
"gotest.tools/assert"
|
||||
is "gotest.tools/assert/cmp"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
func durationPtr(d time.Duration) *time.Duration {
|
||||
return &d
|
||||
}
|
||||
|
||||
func TestQueueMaxRetries(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
logger := logrus.New()
|
||||
logger.SetLevel(logrus.DebugLevel)
|
||||
ctx = log.WithLogger(ctx, logruslogger.FromLogrus(logrus.NewEntry(logger)))
|
||||
n := 0
|
||||
knownErr := errors.New("Testing error")
|
||||
handler := func(ctx context.Context, key string) error {
|
||||
n++
|
||||
return knownErr
|
||||
}
|
||||
wq := New(workqueue.NewMaxOfRateLimiter(
|
||||
// The default upper bound is 1000 seconds. Let's not use that.
|
||||
workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 10*time.Millisecond),
|
||||
&workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
|
||||
), t.Name(), handler, nil)
|
||||
wq.Enqueue(context.TODO(), "test")
|
||||
|
||||
for n < MaxRetries {
|
||||
assert.Assert(t, wq.handleQueueItem(ctx))
|
||||
}
|
||||
|
||||
assert.Assert(t, is.Equal(n, MaxRetries))
|
||||
assert.Assert(t, is.Equal(0, wq.Len()))
|
||||
}
|
||||
|
||||
func TestQueueCustomRetries(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
logger := logrus.New()
|
||||
logger.SetLevel(logrus.DebugLevel)
|
||||
ctx = log.WithLogger(ctx, logruslogger.FromLogrus(logrus.NewEntry(logger)))
|
||||
n := 0
|
||||
errorSeen := 0
|
||||
retryTestError := errors.New("Error should be retried every 10 milliseconds")
|
||||
handler := func(ctx context.Context, key string) error {
|
||||
if key == "retrytest" {
|
||||
n++
|
||||
return retryTestError
|
||||
}
|
||||
return errors.New("Unknown error")
|
||||
}
|
||||
|
||||
shouldRetryFunc := func(ctx context.Context, key string, timesTried int, originallyAdded time.Time, err error) (*time.Duration, error) {
|
||||
var sleepTime *time.Duration
|
||||
if errors.Is(err, retryTestError) {
|
||||
errorSeen++
|
||||
sleepTime = durationPtr(10 * time.Millisecond)
|
||||
}
|
||||
_, retErr := DefaultRetryFunc(ctx, key, timesTried, originallyAdded, err)
|
||||
return sleepTime, retErr
|
||||
}
|
||||
|
||||
wq := New(&workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(1000), 1000)}, t.Name(), handler, shouldRetryFunc)
|
||||
|
||||
timeTaken := func(key string) time.Duration {
|
||||
start := time.Now()
|
||||
wq.Enqueue(context.TODO(), key)
|
||||
for i := 0; i < MaxRetries; i++ {
|
||||
assert.Assert(t, wq.handleQueueItem(ctx))
|
||||
}
|
||||
return time.Since(start)
|
||||
}
|
||||
|
||||
unknownTime := timeTaken("unknown")
|
||||
assert.Assert(t, n == 0)
|
||||
assert.Assert(t, unknownTime < 10*time.Millisecond)
|
||||
|
||||
retrytestTime := timeTaken("retrytest")
|
||||
assert.Assert(t, is.Equal(n, MaxRetries))
|
||||
assert.Assert(t, is.Equal(errorSeen, MaxRetries))
|
||||
|
||||
assert.Assert(t, is.Equal(0, wq.Len()))
|
||||
assert.Assert(t, retrytestTime > 10*time.Millisecond*time.Duration(n-1))
|
||||
assert.Assert(t, retrytestTime < 2*10*time.Millisecond*time.Duration(n-1))
|
||||
}
|
||||
|
||||
func TestForget(t *testing.T) {
|
||||
t.Parallel()
|
||||
handler := func(ctx context.Context, key string) error {
|
||||
panic("Should never be called")
|
||||
}
|
||||
wq := New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), handler, nil)
|
||||
|
||||
wq.Forget(context.TODO(), "val")
|
||||
assert.Assert(t, is.Equal(0, wq.Len()))
|
||||
|
||||
v := "test"
|
||||
wq.EnqueueWithoutRateLimit(context.TODO(), v)
|
||||
assert.Assert(t, is.Equal(1, wq.Len()))
|
||||
}
|
||||
|
||||
func TestQueueEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
q := New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
item, err := q.getNextItem(ctx)
|
||||
assert.Error(t, err, context.DeadlineExceeded.Error())
|
||||
assert.Assert(t, is.Nil(item))
|
||||
}
|
||||
|
||||
func TestQueueItemNoSleep(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1000*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
q := New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
q.lock.Lock()
|
||||
q.insert(ctx, "foo", false, durationPtr(-1*time.Hour))
|
||||
q.insert(ctx, "bar", false, durationPtr(-1*time.Hour))
|
||||
q.lock.Unlock()
|
||||
|
||||
item, err := q.getNextItem(ctx)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, is.Equal(item.key, "foo"))
|
||||
|
||||
item, err = q.getNextItem(ctx)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, is.Equal(item.key, "bar"))
|
||||
}
|
||||
|
||||
func TestQueueItemSleep(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1000*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
q := New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
return nil
|
||||
}, nil)
|
||||
q.lock.Lock()
|
||||
q.insert(ctx, "foo", false, durationPtr(100*time.Millisecond))
|
||||
q.insert(ctx, "bar", false, durationPtr(100*time.Millisecond))
|
||||
q.lock.Unlock()
|
||||
|
||||
item, err := q.getNextItem(ctx)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, is.Equal(item.key, "foo"))
|
||||
}
|
||||
|
||||
func TestQueueBackgroundAdd(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5000*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
q := New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
return nil
|
||||
}, nil)
|
||||
start := time.Now()
|
||||
time.AfterFunc(100*time.Millisecond, func() {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
q.insert(ctx, "foo", false, nil)
|
||||
})
|
||||
|
||||
item, err := q.getNextItem(ctx)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, is.Equal(item.key, "foo"))
|
||||
assert.Assert(t, time.Since(start) > 100*time.Millisecond)
|
||||
}
|
||||
|
||||
func TestQueueBackgroundAdvance(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5000*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
q := New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
return nil
|
||||
}, nil)
|
||||
start := time.Now()
|
||||
q.lock.Lock()
|
||||
q.insert(ctx, "foo", false, durationPtr(10*time.Second))
|
||||
q.lock.Unlock()
|
||||
|
||||
time.AfterFunc(200*time.Millisecond, func() {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
q.insert(ctx, "foo", false, nil)
|
||||
})
|
||||
|
||||
item, err := q.getNextItem(ctx)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, is.Equal(item.key, "foo"))
|
||||
assert.Assert(t, time.Since(start) > 200*time.Millisecond)
|
||||
assert.Assert(t, time.Since(start) < 5*time.Second)
|
||||
}
|
||||
|
||||
func TestQueueRedirty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5000*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
var times int64
|
||||
var q *Queue
|
||||
q = New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
assert.Assert(t, is.Equal(key, "foo"))
|
||||
if atomic.AddInt64(×, 1) == 1 {
|
||||
q.EnqueueWithoutRateLimit(context.TODO(), "foo")
|
||||
} else {
|
||||
cancel()
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
q.EnqueueWithoutRateLimit(context.TODO(), "foo")
|
||||
q.Run(ctx, 1)
|
||||
for !q.Empty() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
assert.Assert(t, is.Equal(atomic.LoadInt64(×), int64(2)))
|
||||
}
|
||||
|
||||
func TestHeapConcurrency(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5000*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
start := time.Now()
|
||||
seen := sync.Map{}
|
||||
q := New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
seen.Store(key, struct{}{})
|
||||
time.Sleep(time.Second)
|
||||
return nil
|
||||
}, nil)
|
||||
for i := 0; i < 20; i++ {
|
||||
q.EnqueueWithoutRateLimit(context.TODO(), strconv.Itoa(i))
|
||||
}
|
||||
|
||||
assert.Assert(t, q.Len() == 20)
|
||||
|
||||
go q.Run(ctx, 20)
|
||||
for q.Len() > 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
_, ok := seen.Load(strconv.Itoa(i))
|
||||
assert.Assert(t, ok, "Did not observe: %d", i)
|
||||
}
|
||||
assert.Assert(t, time.Since(start) < 5*time.Second)
|
||||
}
|
||||
|
||||
func checkConsistency(t *testing.T, q *Queue) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
for next := q.items.Front(); next != nil && next.Next() != nil; next = next.Next() {
|
||||
qi := next.Value.(*queueItem)
|
||||
qiNext := next.Next().Value.(*queueItem)
|
||||
assert.Assert(t, qi.plannedToStartWorkAt.Before(qiNext.plannedToStartWorkAt) || qi.plannedToStartWorkAt.Equal(qiNext.plannedToStartWorkAt))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeapOrder(t *testing.T) {
|
||||
q := New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
return nil
|
||||
}, nil)
|
||||
q.clock = nonmovingClock{}
|
||||
|
||||
q.EnqueueWithoutRateLimitWithDelay(context.TODO(), "a", 1000)
|
||||
q.EnqueueWithoutRateLimitWithDelay(context.TODO(), "b", 2000)
|
||||
q.EnqueueWithoutRateLimitWithDelay(context.TODO(), "c", 3000)
|
||||
q.EnqueueWithoutRateLimitWithDelay(context.TODO(), "d", 4000)
|
||||
q.EnqueueWithoutRateLimitWithDelay(context.TODO(), "e", 5000)
|
||||
checkConsistency(t, q)
|
||||
t.Logf("%v", q)
|
||||
q.EnqueueWithoutRateLimitWithDelay(context.TODO(), "d", 1000)
|
||||
checkConsistency(t, q)
|
||||
t.Logf("%v", q)
|
||||
q.EnqueueWithoutRateLimitWithDelay(context.TODO(), "c", 1001)
|
||||
checkConsistency(t, q)
|
||||
t.Logf("%v", q)
|
||||
q.EnqueueWithoutRateLimitWithDelay(context.TODO(), "e", 999)
|
||||
checkConsistency(t, q)
|
||||
t.Logf("%v", q)
|
||||
}
|
||||
|
||||
type rateLimitWrapper struct {
|
||||
addedMap sync.Map
|
||||
forgottenMap sync.Map
|
||||
rl workqueue.RateLimiter
|
||||
}
|
||||
|
||||
func (r *rateLimitWrapper) When(item interface{}) time.Duration {
|
||||
if _, ok := r.forgottenMap.Load(item); ok {
|
||||
r.forgottenMap.Delete(item)
|
||||
// Reset the added map
|
||||
r.addedMap.Store(item, 1)
|
||||
} else {
|
||||
actual, loaded := r.addedMap.LoadOrStore(item, 1)
|
||||
if loaded {
|
||||
r.addedMap.Store(item, actual.(int)+1)
|
||||
}
|
||||
}
|
||||
|
||||
return r.rl.When(item)
|
||||
}
|
||||
|
||||
func (r *rateLimitWrapper) Forget(item interface{}) {
|
||||
r.forgottenMap.Store(item, struct{}{})
|
||||
r.rl.Forget(item)
|
||||
}
|
||||
|
||||
func (r *rateLimitWrapper) NumRequeues(item interface{}) int {
|
||||
return r.rl.NumRequeues(item)
|
||||
}
|
||||
|
||||
func TestRateLimiter(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5000*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
syncMap := sync.Map{}
|
||||
syncMap.Store("foo", 0)
|
||||
syncMap.Store("bar", 0)
|
||||
syncMap.Store("baz", 0)
|
||||
syncMap.Store("quux", 0)
|
||||
|
||||
start := time.Now()
|
||||
ratelimiter := &rateLimitWrapper{
|
||||
rl: workqueue.NewItemFastSlowRateLimiter(1*time.Millisecond, 100*time.Millisecond, 1),
|
||||
}
|
||||
|
||||
q := New(ratelimiter, t.Name(), func(ctx context.Context, key string) error {
|
||||
oldValue, _ := syncMap.Load(key)
|
||||
syncMap.Store(key, oldValue.(int)+1)
|
||||
if oldValue.(int) < 9 {
|
||||
return errors.New("test")
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
enqueued := 0
|
||||
syncMap.Range(func(key, value interface{}) bool {
|
||||
enqueued++
|
||||
q.Enqueue(context.TODO(), key.(string))
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Assert(t, enqueued == 4)
|
||||
go q.Run(ctx, 10)
|
||||
|
||||
incomplete := true
|
||||
for incomplete {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
incomplete = false
|
||||
// Wait for all items to finish processing.
|
||||
syncMap.Range(func(key, value interface{}) bool {
|
||||
if value.(int) < 10 {
|
||||
incomplete = true
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Make sure there were ~9 "slow" rate limits per item, and 1 fast
|
||||
assert.Assert(t, time.Since(start) > 9*100*time.Millisecond)
|
||||
// Make sure we didn't go off the deep end.
|
||||
assert.Assert(t, time.Since(start) < 2*9*100*time.Millisecond)
|
||||
|
||||
// Make sure each item was seen. And Forgotten.
|
||||
syncMap.Range(func(key, value interface{}) bool {
|
||||
_, ok := ratelimiter.forgottenMap.Load(key)
|
||||
assert.Assert(t, ok, "%s in forgotten map", key)
|
||||
val, ok := ratelimiter.addedMap.Load(key)
|
||||
assert.Assert(t, ok, "%s in added map", key)
|
||||
assert.Assert(t, val == 10)
|
||||
return true
|
||||
})
|
||||
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
assert.Assert(t, len(q.itemsInQueue) == 0)
|
||||
assert.Assert(t, len(q.itemsBeingProcessed) == 0)
|
||||
assert.Assert(t, q.items.Len() == 0)
|
||||
|
||||
}
|
||||
|
||||
func TestQueueForgetInProgress(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var times int64
|
||||
var q *Queue
|
||||
q = New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
assert.Assert(t, is.Equal(key, "foo"))
|
||||
atomic.AddInt64(×, 1)
|
||||
q.Forget(context.TODO(), key)
|
||||
return errors.New("test")
|
||||
}, nil)
|
||||
|
||||
q.EnqueueWithoutRateLimit(context.TODO(), "foo")
|
||||
go q.Run(ctx, 1)
|
||||
for !q.Empty() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
assert.Assert(t, is.Equal(atomic.LoadInt64(×), int64(1)))
|
||||
}
|
||||
|
||||
func TestQueueForgetBeforeStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
q := New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
panic("shouldn't be called")
|
||||
}, nil)
|
||||
|
||||
q.EnqueueWithoutRateLimit(context.TODO(), "foo")
|
||||
q.Forget(context.TODO(), "foo")
|
||||
go q.Run(ctx, 1)
|
||||
for !q.Empty() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueueMoveItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
q := New(workqueue.DefaultItemBasedRateLimiter(), t.Name(), func(ctx context.Context, key string) error {
|
||||
panic("shouldn't be called")
|
||||
}, nil)
|
||||
q.clock = nonmovingClock{}
|
||||
|
||||
q.insert(ctx, "foo", false, durationPtr(3000))
|
||||
q.insert(ctx, "bar", false, durationPtr(2000))
|
||||
q.insert(ctx, "baz", false, durationPtr(1000))
|
||||
checkConsistency(t, q)
|
||||
t.Log(q)
|
||||
|
||||
q.insert(ctx, "foo", false, durationPtr(2000))
|
||||
checkConsistency(t, q)
|
||||
t.Log(q)
|
||||
|
||||
q.insert(ctx, "foo", false, durationPtr(1999))
|
||||
checkConsistency(t, q)
|
||||
t.Log(q)
|
||||
|
||||
q.insert(ctx, "foo", false, durationPtr(999))
|
||||
checkConsistency(t, q)
|
||||
t.Log(q)
|
||||
}
|
||||
|
||||
type nonmovingClock struct {
|
||||
}
|
||||
|
||||
func (n nonmovingClock) Now() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (n nonmovingClock) Since(t time.Time) time.Duration {
|
||||
return n.Now().Sub(t)
|
||||
}
|
||||
|
||||
func (n nonmovingClock) After(d time.Duration) <-chan time.Time {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (n nonmovingClock) NewTimer(d time.Duration) clock.Timer {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (n nonmovingClock) Sleep(d time.Duration) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (n nonmovingClock) Tick(d time.Duration) <-chan time.Time {
|
||||
panic("implement me")
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user