mirror of
https://github.com/sergelogvinov/proxmox-cloud-controller-manager.git
synced 2026-03-03 02:14:48 +00:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66c8f6e1d1 | ||
|
|
ffec772a85 | ||
|
|
88fad844c7 | ||
|
|
344118960d | ||
|
|
704aacce5a | ||
|
|
ba7a61181a | ||
|
|
96e3332893 | ||
|
|
5fded7f1c8 | ||
|
|
db3781fd15 | ||
|
|
8923f5d852 | ||
|
|
34d39261b2 | ||
|
|
62d0bb89e2 | ||
|
|
71174a0105 | ||
|
|
4384e5146f | ||
|
|
66d2e70230 | ||
|
|
1356bd871f | ||
|
|
3983d5ba10 | ||
|
|
63418b0117 | ||
|
|
c9f619ff96 | ||
|
|
fced446f46 | ||
|
|
a33ea6ead7 | ||
|
|
706faa8d08 | ||
|
|
0a31716c17 | ||
|
|
dac1775cf2 | ||
|
|
01e3ce854c | ||
|
|
d2181a88f6 | ||
|
|
0bc8801146 | ||
|
|
0cf1a40802 | ||
|
|
0cfad86361 | ||
|
|
c8be20eb8d | ||
|
|
27c3e627c4 | ||
|
|
229be1432a | ||
|
|
b77455af4d | ||
|
|
2066aa885e | ||
|
|
8ef4bcea69 | ||
|
|
144b1c74e6 | ||
|
|
1ce4ade1c6 | ||
|
|
e1b8e9b419 | ||
|
|
a8183c8df4 | ||
|
|
60f953d1da | ||
|
|
2ebbf7a9d5 | ||
|
|
628e7d6500 | ||
|
|
7aba46727d | ||
|
|
e664b24029 | ||
|
|
efb753c9de | ||
|
|
5a645a25c3 | ||
|
|
2e35df2db0 | ||
|
|
646d77633f | ||
|
|
19e1f44996 | ||
|
|
0f0374c2eb | ||
|
|
3a34fb960a | ||
|
|
8a2f51844c | ||
|
|
ca452ad040 | ||
|
|
bb868bcbd7 | ||
|
|
956a30a463 | ||
|
|
63eef87a87 | ||
|
|
710dc1b740 | ||
|
|
5ea7b738d3 | ||
|
|
2bfb088528 | ||
|
|
87baa50bf0 | ||
|
|
7ec261758c | ||
|
|
64fc662d00 | ||
|
|
b3767b515b | ||
|
|
10f3e365d2 | ||
|
|
2b6435273f | ||
|
|
63b6907413 | ||
|
|
4d79e4e00a | ||
|
|
5876cd4c7b | ||
|
|
b81ad1406d | ||
|
|
e31b24cf19 | ||
|
|
e1e52630ff | ||
|
|
76dae8707b | ||
|
|
c02bc2f368 | ||
|
|
ce92b3eef0 | ||
|
|
47717693b5 | ||
|
|
12d2858984 | ||
|
|
3c7cd44967 | ||
|
|
36757fc0be | ||
|
|
c1ab34cba5 | ||
|
|
d1e6e705bc | ||
|
|
9ba9ff27dd | ||
|
|
677e6cc330 | ||
|
|
a752d1056d | ||
|
|
de5598648f | ||
|
|
10592d13b4 | ||
|
|
7b73b5f8a2 | ||
|
|
6f0c667c16 | ||
|
|
ac2f564e43 | ||
|
|
41a7f8d8df | ||
|
|
74d8c78099 | ||
|
|
a76b7c28da | ||
|
|
93d8edc6b3 | ||
|
|
4f7aaeb0c3 | ||
|
|
eef9c9cd2e | ||
|
|
d54368e14f | ||
|
|
3a3c0708d8 | ||
|
|
5c1a38234c | ||
|
|
75ead904a3 |
@@ -8,11 +8,10 @@ policies:
|
||||
invalidLastCharacters: .
|
||||
body:
|
||||
required: true
|
||||
dco: false
|
||||
gpg: false
|
||||
dco: true
|
||||
spellcheck:
|
||||
locale: US
|
||||
maximumOfOneCommit: false
|
||||
maximumOfOneCommit: true
|
||||
conventional:
|
||||
types:
|
||||
- build
|
||||
@@ -27,11 +26,16 @@ policies:
|
||||
scopes:
|
||||
- deps
|
||||
- main
|
||||
- chart
|
||||
descriptionLength: 72
|
||||
- type: license
|
||||
spec:
|
||||
skipPaths:
|
||||
- .git/
|
||||
includeSuffixes:
|
||||
- .go
|
||||
excludeSuffixes:
|
||||
- .pb.go
|
||||
allowPrecedingComments: false
|
||||
header: |
|
||||
/*
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
@@ -16,3 +16,5 @@ assignees: ""
|
||||
|
||||
- Plugin version:
|
||||
- Kubernetes version: [`kubectl version --short`]
|
||||
- Node describe: [`kubectl describe node <node>`]
|
||||
- OS version [`cat /etc/os-release`]
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
vendored
5
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
vendored
@@ -9,3 +9,8 @@ assignees: ""
|
||||
## Feature Request
|
||||
|
||||
### Description
|
||||
|
||||
### Community Note
|
||||
|
||||
* Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
|
||||
* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -4,7 +4,7 @@
|
||||
## Note to the Contributor
|
||||
|
||||
We encourage contributors to go through a proposal process to discuss major changes.
|
||||
Before your PR is allowed to run through CI, the maintainers of Talos CCM will first have to approve the PR.
|
||||
Before your PR is allowed to run through CI, the maintainers of Proxmox CCM will first have to approve the PR.
|
||||
-->
|
||||
|
||||
## What? (description)
|
||||
|
||||
14
.github/dependabot.yml
vendored
14
.github/dependabot.yml
vendored
@@ -8,10 +8,10 @@ updates:
|
||||
directory: "/"
|
||||
commit-message:
|
||||
prefix: "chore:"
|
||||
open-pull-requests-limit: 5
|
||||
open-pull-requests-limit: 8
|
||||
rebase-strategy: disabled
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
interval: "monthly"
|
||||
day: "monday"
|
||||
time: "08:00"
|
||||
timezone: "UTC"
|
||||
@@ -20,10 +20,10 @@ updates:
|
||||
directory: "/"
|
||||
commit-message:
|
||||
prefix: "chore:"
|
||||
open-pull-requests-limit: 5
|
||||
open-pull-requests-limit: 8
|
||||
rebase-strategy: disabled
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
interval: "monthly"
|
||||
day: "monday"
|
||||
time: "07:00"
|
||||
timezone: "UTC"
|
||||
@@ -36,15 +36,17 @@ updates:
|
||||
- "k8s.io/client-go"
|
||||
- "k8s.io/cloud-provider"
|
||||
- "k8s.io/component-base"
|
||||
- "k8s.io/component-helpers"
|
||||
- "k8s.io/controller-manager"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
commit-message:
|
||||
prefix: "chore:"
|
||||
open-pull-requests-limit: 5
|
||||
open-pull-requests-limit: 8
|
||||
rebase-strategy: disabled
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
interval: "monthly"
|
||||
day: "monday"
|
||||
time: "07:00"
|
||||
timezone: "UTC"
|
||||
|
||||
15
.github/workflows/build-edge.yaml
vendored
15
.github/workflows/build-edge.yaml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Build edge
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -15,21 +16,27 @@ jobs:
|
||||
build-publish:
|
||||
name: "Build image and publish"
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: main
|
||||
- name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.1.2
|
||||
uses: sigstore/cosign-installer@v4.0.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm64
|
||||
- name: Set up docker buildx
|
||||
run: make docker-init
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Github registry login
|
||||
uses: docker/login-action@v3
|
||||
|
||||
12
.github/workflows/build-test.yaml
vendored
12
.github/workflows/build-test.yaml
vendored
@@ -15,24 +15,24 @@ jobs:
|
||||
build:
|
||||
name: Build
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up go
|
||||
timeout-minutes: 5
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v1.53.3
|
||||
args: --config=.golangci.yml
|
||||
version: v2.8.0
|
||||
args: --timeout=5m --config=.golangci.yml
|
||||
- name: Unit
|
||||
run: make unit
|
||||
- name: Build
|
||||
|
||||
6
.github/workflows/charts.yaml
vendored
6
.github/workflows/charts.yaml
vendored
@@ -11,16 +11,16 @@ jobs:
|
||||
helm-lint:
|
||||
name: Helm chart check
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- name: Install chart-testing tools
|
||||
id: lint
|
||||
uses: helm/chart-testing-action@v2.4.0
|
||||
uses: helm/chart-testing-action@v2.8.0
|
||||
|
||||
- name: Run helm chart linter
|
||||
run: ct --config hack/ct.yml lint
|
||||
|
||||
8
.github/workflows/conform.yaml
vendored
8
.github/workflows/conform.yaml
vendored
@@ -9,10 +9,10 @@ jobs:
|
||||
conform:
|
||||
name: Conformance
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
@@ -20,4 +20,6 @@ jobs:
|
||||
run: git fetch --no-tags origin main:main
|
||||
|
||||
- name: Conform action
|
||||
uses: talos-systems/conform@v0.1.0-alpha.27
|
||||
uses: talos-systems/conform@v0.1.0-alpha.30
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
10
.github/workflows/release-charts.yaml
vendored
10
.github/workflows/release-charts.yaml
vendored
@@ -11,23 +11,23 @@ jobs:
|
||||
build-publish:
|
||||
name: "Publish helm chart"
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
uses: azure/setup-helm@v4
|
||||
with:
|
||||
version: v3.12.2
|
||||
version: v3.13.3
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.1.2
|
||||
uses: sigstore/cosign-installer@v4.0.0
|
||||
|
||||
- name: Github registry login
|
||||
uses: docker/login-action@v3
|
||||
|
||||
22
.github/workflows/release-please.yml
vendored
Normal file
22
.github/workflows/release-please.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Release please
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
release-please:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Create release PR
|
||||
id: release
|
||||
uses: googleapis/release-please-action@v4
|
||||
with:
|
||||
config-file: hack/release-please-config.json
|
||||
manifest-file: hack/release-please-manifest.json
|
||||
12
.github/workflows/release-pre.yaml
vendored
12
.github/workflows/release-pre.yaml
vendored
@@ -8,25 +8,25 @@ on:
|
||||
jobs:
|
||||
build-publish:
|
||||
name: "Check release docs"
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.head_ref, 'release-')
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- name: Release version
|
||||
shell: bash
|
||||
id: release
|
||||
run: |
|
||||
echo "TAG=v${GITHUB_HEAD_REF:8}" >> "$GITHUB_ENV"
|
||||
if: startsWith(github.head_ref, 'release-please')
|
||||
run: jq -r '"TAG=v"+.[]' hack/release-please-manifest.json >> "$GITHUB_ENV"
|
||||
|
||||
- name: Helm docs
|
||||
uses: gabe565/setup-helm-docs-action@v1
|
||||
with:
|
||||
version: v1.11.3
|
||||
|
||||
- name: Generate
|
||||
run: make docs
|
||||
|
||||
25
.github/workflows/release.yaml
vendored
25
.github/workflows/release.yaml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
@@ -9,21 +10,25 @@ jobs:
|
||||
build-publish:
|
||||
name: "Build image and publish"
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.1.2
|
||||
uses: sigstore/cosign-installer@v4.0.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm64
|
||||
- name: Set up docker buildx
|
||||
run: make docker-init
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Github registry login
|
||||
uses: docker/login-action@v3
|
||||
@@ -32,6 +37,18 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push edge
|
||||
timeout-minutes: 10
|
||||
run: make images
|
||||
env:
|
||||
PUSH: "true"
|
||||
TAG: "edge"
|
||||
- name: Sign images
|
||||
timeout-minutes: 4
|
||||
run: make images-cosign
|
||||
env:
|
||||
TAG: "edge"
|
||||
|
||||
- name: Build and push
|
||||
timeout-minutes: 10
|
||||
run: make images
|
||||
|
||||
21
.github/workflows/stale.yaml
vendored
Normal file
21
.github/workflows/stale.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Close stale issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 8 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
name: Check stale issues
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
stale-issue-message: This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 14 days.
|
||||
close-issue-message: This issue was closed because it has been stalled for 14 days with no activity.
|
||||
days-before-issue-stale: 180
|
||||
days-before-issue-close: 14
|
||||
days-before-pr-close: -1 # never close PRs
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
/charts/proxmox-cloud-controller-manager/values-dev.yaml
|
||||
/proxmox-cloud-controller-manager*
|
||||
/kubeconfig
|
||||
/kubeconfig*
|
||||
/proxmox-config.yaml
|
||||
#
|
||||
|
||||
|
||||
288
.golangci.yml
288
.golangci.yml
@@ -1,205 +1,147 @@
|
||||
# This file contains all available configuration options
|
||||
# with their default values.
|
||||
|
||||
# options for analysis running
|
||||
version: "2"
|
||||
run:
|
||||
go: '1.20'
|
||||
# default concurrency is a available CPU number
|
||||
# concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 10m
|
||||
|
||||
# exit code when at least one issue was found, default is 1
|
||||
issues-exit-code: 1
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: true
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
skip-files:
|
||||
- charts/
|
||||
- docs/
|
||||
|
||||
# list of build tags, all linters use it. Default is empty list.
|
||||
build-tags:
|
||||
- integration
|
||||
- integration_api
|
||||
- integration_cli
|
||||
- integration_k8s
|
||||
- integration_provision
|
||||
|
||||
# output configuration options
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number"
|
||||
format: line-number
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-type-assertions: true
|
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-blank: true
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: true
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
simplify: true
|
||||
gocyclo:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 15
|
||||
maligned:
|
||||
# print struct with more effective memory layout or not, false by default
|
||||
suggest-new: true
|
||||
dupl:
|
||||
# tokens count to trigger issue, 150 by default
|
||||
threshold: 100
|
||||
goconst:
|
||||
# minimal length of string constant, 3 by default
|
||||
min-len: 3
|
||||
# minimal occurrences count to trigger, 3 by default
|
||||
min-occurrences: 3
|
||||
depguard:
|
||||
list-type: blacklist
|
||||
misspell:
|
||||
# Correct spellings using locale preferences for US or UK.
|
||||
# Default is to use a neutral variety of English.
|
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||
locale: US
|
||||
lll:
|
||||
# max line length, lines longer will be reported. Default is 120.
|
||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
||||
line-length: 200
|
||||
# tab width in spaces. Default to 1.
|
||||
tab-width: 1
|
||||
unused:
|
||||
# treat code as a program (not a library) and report unused exported identifiers; default is false.
|
||||
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
unparam:
|
||||
# call graph construction algorithm (cha, rta). In general, use cha for libraries,
|
||||
# and rta for programs with main packages. Default is cha.
|
||||
algo: cha
|
||||
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
nakedret:
|
||||
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
|
||||
max-func-lines: 30
|
||||
nolintlint:
|
||||
allow-unused: false
|
||||
allow-leading-space: false
|
||||
allow-no-explanation: []
|
||||
require-explanation: false
|
||||
require-specific: true
|
||||
prealloc:
|
||||
# XXX: we don't recommend using this linter before doing performance profiling.
|
||||
# For most programs usage of prealloc will be a premature optimization.
|
||||
|
||||
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
|
||||
# True by default.
|
||||
simple: true
|
||||
range-loops: true # Report preallocation suggestions on range loops, true by default
|
||||
for-loops: false # Report preallocation suggestions on for loops, false by default
|
||||
gci:
|
||||
sections:
|
||||
- standard # Captures all standard packages if they do not match another section.
|
||||
- default # Contains all imports that could not be matched to another section type.
|
||||
- prefix(github.com/sergelogvinov) # Groups all imports with the specified Prefix.
|
||||
- prefix(k8s.io) # Groups all imports with the specified Prefix.
|
||||
cyclop:
|
||||
# the maximal code complexity to report
|
||||
max-complexity: 20
|
||||
gomoddirectives:
|
||||
replace-local: true
|
||||
replace-allow-list: []
|
||||
retract-allow-no-explanation: false
|
||||
exclude-forbidden: true
|
||||
|
||||
formats:
|
||||
text:
|
||||
path: stdout
|
||||
print-linter-name: true
|
||||
print-issued-lines: true
|
||||
colors: false
|
||||
linters:
|
||||
enable-all: true
|
||||
default: all
|
||||
disable:
|
||||
- depguard
|
||||
- errorlint
|
||||
- exhaustruct
|
||||
- exhaustivestruct
|
||||
- err113
|
||||
- forbidigo
|
||||
- forcetypeassert
|
||||
- funlen
|
||||
- gas
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gocognit
|
||||
- godox
|
||||
- goerr113
|
||||
- gomnd
|
||||
- ifshort
|
||||
- ireturn # we return interfaces
|
||||
- godot
|
||||
- gosec
|
||||
- inamedparam
|
||||
- ireturn
|
||||
- maintidx
|
||||
- mnd
|
||||
- musttag
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilnil # we return "nil, nil"
|
||||
- nonamedreturns
|
||||
- nilnil
|
||||
- nolintlint
|
||||
- nosnakecase
|
||||
- nonamedreturns
|
||||
- paralleltest
|
||||
- promlinter # https://github.com/golangci/golangci-lint/issues/2222
|
||||
- tagliatelle # we have many different conventions
|
||||
- perfsprint
|
||||
- promlinter
|
||||
- protogetter
|
||||
- recvcheck
|
||||
- tagalign
|
||||
- tagliatelle
|
||||
- testifylint
|
||||
- testpackage
|
||||
- thelper
|
||||
- typecheck
|
||||
- varnamelen # too annoying
|
||||
- varnamelen
|
||||
- wrapcheck
|
||||
- contextcheck # enable once golangci-lint 1.50.1 or 1.51 lands
|
||||
|
||||
# abandoned linters for which golangci shows the warning that the repo is archived by the owner
|
||||
- golint
|
||||
- interfacer
|
||||
- maligned
|
||||
- scopelint
|
||||
|
||||
disable-all: false
|
||||
fast: false
|
||||
- wsl
|
||||
|
||||
# temporarily disabled linters
|
||||
- copyloopvar
|
||||
- intrange
|
||||
- noinlineerr
|
||||
settings:
|
||||
importas:
|
||||
alias:
|
||||
- pkg: github.com/sergelogvinov/proxmox-cloud-controller/manager/metrics
|
||||
alias: metrics
|
||||
- pkg: github.com/sergelogvinov/proxmox-cloud-controller/proxmoxpool
|
||||
alias: proxmoxpool
|
||||
- pkg: github.com/sergelogvinov/proxmox-cloud-controller/proxmox
|
||||
alias: proxmox
|
||||
- pkg: github.com/sergelogvinov/proxmox-cloud-controller/provider
|
||||
alias: provider
|
||||
- pkg: github.com/sergelogvinov/proxmox-cloud-controller/config
|
||||
alias: providerconfig
|
||||
wsl_v5:
|
||||
allow-first-in-block: true
|
||||
allow-whole-block: false
|
||||
branch-max-lines: 2
|
||||
disable:
|
||||
- err
|
||||
cyclop:
|
||||
max-complexity: 30
|
||||
dupl:
|
||||
threshold: 150
|
||||
errcheck:
|
||||
check-type-assertions: false
|
||||
check-blank: true
|
||||
exclude-functions:
|
||||
- fmt.Fprintln
|
||||
- fmt.Fprintf
|
||||
- fmt.Fprint
|
||||
goconst:
|
||||
min-len: 3
|
||||
min-occurrences: 3
|
||||
gocyclo:
|
||||
min-complexity: 30
|
||||
gomoddirectives:
|
||||
replace-local: true
|
||||
replace-allow-list: []
|
||||
retract-allow-no-explanation: false
|
||||
exclude-forbidden: true
|
||||
lll:
|
||||
line-length: 200
|
||||
tab-width: 1
|
||||
misspell:
|
||||
locale: US
|
||||
nolintlint:
|
||||
require-explanation: false
|
||||
require-specific: true
|
||||
allow-unused: false
|
||||
prealloc:
|
||||
simple: true
|
||||
range-loops: true
|
||||
for-loops: false
|
||||
staticcheck:
|
||||
checks:
|
||||
[
|
||||
"all",
|
||||
"-ST1000",
|
||||
"-ST1003",
|
||||
"-ST1016",
|
||||
"-ST1020",
|
||||
"-ST1021",
|
||||
"-ST1022",
|
||||
"-QF1001",
|
||||
"-QF1008",
|
||||
]
|
||||
unused:
|
||||
local-variables-are-used: false
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# But independently from this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`
|
||||
exclude:
|
||||
- package comment should be of the form "Package services ..." # revive
|
||||
- ^ST1000 # ST1000: at least one file in a package should have a package comment (stylecheck)
|
||||
|
||||
exclude-rules:
|
||||
|
||||
# Independently from option `exclude` we use default exclude patterns,
|
||||
# it can be disabled by this option. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`.
|
||||
# Default value for this option is true.
|
||||
exclude-use-default: false
|
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-issues-per-linter: 0
|
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||
max-same-issues: 0
|
||||
|
||||
# Show only new issues: if there are unstaged changes or untracked files,
|
||||
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
|
||||
# It's a super-useful option for integration of golangci-lint into existing
|
||||
# large codebase. It's not practical to fix all existing issues at the moment
|
||||
# of integration: much better don't allow issues in new code.
|
||||
# Default is false.
|
||||
uniq-by-line: true
|
||||
new: false
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard # Captures all standard packages if they do not match another section.
|
||||
- default # Contains all imports that could not be matched to another section type.
|
||||
- prefix(github.com/sergelogvinov) # Groups all imports with the specified Prefix.
|
||||
- prefix(k8s.io) # Groups all imports with the specified Prefix.
|
||||
|
||||
286
CHANGELOG.md
286
CHANGELOG.md
@@ -1,6 +1,283 @@
|
||||
<a name="v0.11.0"></a>
|
||||
## [0.13.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.12.3...v0.13.0) (2026-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* force label update ([704aacc](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/704aacce5a776257201bb1037e909339062b2151))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **chart:** role binding ([ba7a611](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/ba7a61181add80a838e3d010feab06a304ef98f9))
|
||||
* service account name ([88fad84](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/88fad844c72271c40dccd67b46dead69fb4f603c))
|
||||
|
||||
## [0.12.3](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.12.2...v0.12.3) (2026-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* reduce api calls ([8923f5d](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/8923f5d852c8e376ac7081953158f597a7e6b930))
|
||||
|
||||
## [0.12.2](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.12.1...v0.12.2) (2025-11-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ha-groups ([66d2e70](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/66d2e7023010f517e422a3b56519fb9600afe9dd))
|
||||
|
||||
## [0.12.1](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.12.0...v0.12.1) (2025-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* helm chart release ([3983d5b](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/3983d5ba102afcaa6ec0ad91fdc350c0b2b0e4d3))
|
||||
* release please ([63418b0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/63418b011763fed9620196430bbb9791308bdc30))
|
||||
|
||||
## [0.12.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.11.0...v0.12.0) (2025-11-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add release-please ([a33ea6e](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/a33ea6ead7ea03fc0e2addd2ff74afb5a87936bb))
|
||||
* enhance ha-group handling ([706faa8](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/706faa8d088bb0467770d364b374f060398e9b25))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **chart:** provider value typo ([dac1775](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/dac1775cf2abcf2e8fb2b597a9672bd1c63d26a7))
|
||||
* handle inaccessible nodes ([0a31716](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/0a31716c17dd601fbe36025186de86e2d47e82cd))
|
||||
* log error when instance metadata retrieval fails ([d2181a8](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/d2181a88f6b905544b6a2c9bd4e70e0bbf1da690))
|
||||
* release please ([fced446](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/commit/fced446f46cec5c0d8091ec918d4f4a2c1e6ad0e))
|
||||
|
||||
## [v0.11.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.10.0...v0.11.0) (2025-09-08)
|
||||
|
||||
Welcome to the v0.11.0 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Features
|
||||
|
||||
- use proxmox ha-group as zone name
|
||||
- add extra labels
|
||||
- add config options token_id_file & token_secret_file
|
||||
- add named errors to cloud config
|
||||
|
||||
### Changelog
|
||||
|
||||
* 27c3e62 feat: use proxmox ha-group as zone name
|
||||
* 229be14 feat: add extra labels
|
||||
* b77455a refactor: instance metadata
|
||||
* 2066aa8 chore: bump deps
|
||||
* 8ef4bce feat: add config options token_id_file & token_secret_file
|
||||
* 144b1c7 feat: add named errors to cloud config
|
||||
|
||||
<a name="v0.10.0"></a>
|
||||
## [v0.10.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.9.0...v0.10.0) (2025-08-01)
|
||||
|
||||
Welcome to the v0.10.0 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- makefile conformance stage
|
||||
|
||||
### Features
|
||||
|
||||
- add new network addressing features
|
||||
|
||||
### Changelog
|
||||
|
||||
* 1ce4ade chore: release v0.10.0
|
||||
* e1b8e9b feat: add new network addressing features
|
||||
* a8183c8 refactor: split cloud config module
|
||||
* 60f953d chore: bump deps
|
||||
* 2ebbf7a fix: makefile conformance stage
|
||||
* 628e7d6 chore: clearer error message
|
||||
|
||||
<a name="v0.9.0"></a>
|
||||
## [v0.9.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.8.0...v0.9.0) (2025-06-05)
|
||||
|
||||
Welcome to the v0.9.0 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- cluster vm list
|
||||
|
||||
### Changelog
|
||||
|
||||
* 7aba467 chore: release v0.9.0
|
||||
* e664b24 chore: bump deps
|
||||
* efb753c fix: cluster vm list
|
||||
* 5a645a2 chore: bump deps
|
||||
|
||||
<a name="v0.8.0"></a>
|
||||
## [v0.8.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.7.0...v0.8.0) (2025-04-12)
|
||||
|
||||
Welcome to the v0.8.0 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- find node by name
|
||||
|
||||
### Features
|
||||
|
||||
- custom instance type
|
||||
- **chart:** extra envs values
|
||||
|
||||
### Changelog
|
||||
|
||||
* 2e35df2 chore: release v0.8.0
|
||||
* 646d776 feat(chart): extra envs values
|
||||
* 19e1f44 chore: bump deps
|
||||
* 0f0374c feat: custom instance type
|
||||
* 3a34fb9 fix: find node by name
|
||||
* 8a2f518 chore: bump deps
|
||||
* ca452ad chore: bump deps
|
||||
|
||||
<a name="v0.7.0"></a>
|
||||
## [v0.7.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.6.0...v0.7.0) (2025-01-08)
|
||||
|
||||
Welcome to the v0.7.0 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Features
|
||||
|
||||
- enable support for capmox This makes ccm compatible with cluster api and cluster api provider proxmox (capmox)
|
||||
|
||||
### Changelog
|
||||
|
||||
* bb868bc chore: release v0.7.0
|
||||
* 956a30a feat: enable support for capmox This makes ccm compatible with cluster api and cluster api provider proxmox (capmox)
|
||||
|
||||
<a name="v0.6.0"></a>
|
||||
## [v0.6.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.5.1...v0.6.0) (2025-01-01)
|
||||
|
||||
Welcome to the v0.6.0 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Changelog
|
||||
|
||||
* 63eef87 chore: release v0.6.0
|
||||
* 710dc1b chore: bump deps
|
||||
* 5ea7b73 chore: bump deps
|
||||
* 2bfb088 chore: bump deps
|
||||
* 87baa50 docs: add faq
|
||||
* 7ec2617 docs: install
|
||||
* 64fc662 docs: kubelet flags
|
||||
|
||||
<a name="v0.5.1"></a>
|
||||
## [v0.5.1](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.5.0...v0.5.1) (2024-09-23)
|
||||
|
||||
Welcome to the v0.5.1 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- instance type
|
||||
|
||||
### Changelog
|
||||
|
||||
* b3767b5 chore: release v0.5.1
|
||||
* 10f3e36 fix: instance type
|
||||
* 2b64352 chore(chart): update readme
|
||||
|
||||
<a name="v0.5.0"></a>
|
||||
## [v0.5.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.4.2...v0.5.0) (2024-09-16)
|
||||
|
||||
Welcome to the v0.5.0 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Features
|
||||
|
||||
- find node by uuid
|
||||
- prometheus metrics
|
||||
|
||||
### Changelog
|
||||
|
||||
* 63b6907 chore: release v0.5.0
|
||||
* 4d79e4e docs: install instruction
|
||||
* 5876cd4 feat: find node by uuid
|
||||
* b81ad14 feat: prometheus metrics
|
||||
* e31b24c refactor: contextual logging
|
||||
* e1e5263 chore: bump deps
|
||||
|
||||
<a name="v0.4.2"></a>
|
||||
## [v0.4.2](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.4.1...v0.4.2) (2024-05-04)
|
||||
|
||||
Welcome to the v0.4.2 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Changelog
|
||||
|
||||
* 76dae87 chore: release v0.4.2
|
||||
|
||||
<a name="v0.4.1"></a>
|
||||
## [v0.4.1](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.4.0...v0.4.1) (2024-05-04)
|
||||
|
||||
Welcome to the v0.4.1 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Features
|
||||
|
||||
- **chart:** add daemonset mode
|
||||
- **chart:** add hostAliases and initContainers
|
||||
|
||||
### Changelog
|
||||
|
||||
* c02bc2f chore: release v0.4.1
|
||||
* ce92b3e feat(chart): add daemonset mode
|
||||
* 4771769 chore: bump deps
|
||||
* 12d2858 ci: update multi arch build init
|
||||
* 3c7cd44 ci: update multi arch build init
|
||||
* 36757fc ci: update multi arch build init
|
||||
* c1ab34c chore: bump deps
|
||||
* d1e6e70 docs: update helm install command
|
||||
* 9ba9ff2 feat(chart): add hostAliases and initContainers
|
||||
|
||||
<a name="v0.4.0"></a>
|
||||
## [v0.4.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.3.0...v0.4.0) (2024-02-16)
|
||||
|
||||
Welcome to the v0.4.0 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- init provider
|
||||
|
||||
### Features
|
||||
|
||||
- kubelet dualstack support
|
||||
|
||||
### Changelog
|
||||
|
||||
* 677e6cc chore: release v0.4.0
|
||||
* a752d10 feat: kubelet dualstack support
|
||||
* de55986 fix: init provider
|
||||
* 10592d1 chore: bump deps
|
||||
* 7b73b5f refactor: move providerID to the package
|
||||
|
||||
<a name="v0.3.0"></a>
|
||||
## [v0.3.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.2.0...v0.3.0) (2024-01-03)
|
||||
|
||||
Welcome to the v0.3.0 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- namespace for extension-apiserver-authentication rolebinding
|
||||
|
||||
### Features
|
||||
|
||||
- can use user/password
|
||||
- **chart:** add extraVolumes + extraVolumeMounts
|
||||
|
||||
### Changelog
|
||||
|
||||
* 6f0c667 chore: release v0.3.0
|
||||
* ac2f564 feat: can use user/password
|
||||
* 41a7f8d chore: bump deps
|
||||
* 74d8c78 chore: bump deps
|
||||
* a76b7c2 chore: replace nodeSelector with nodeAffinity in chart + manifests
|
||||
* 93d8edc chore: bump deps
|
||||
* 4f7aaeb chore: bump deps
|
||||
* eef9c9c chore: bump deps
|
||||
* d54368e feat(chart): add extraVolumes + extraVolumeMounts
|
||||
* 3a3c070 chore: bump deps
|
||||
* 5c1a382 fix: namespace for extension-apiserver-authentication rolebinding
|
||||
* 75ead90 chore: bump deps
|
||||
|
||||
<a name="v0.2.0"></a>
|
||||
## [v0.2.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.1.1...v0.2.0) (2023-09-19)
|
||||
## [v0.2.0](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.1.1...v0.2.0) (2023-09-20)
|
||||
|
||||
Welcome to the v0.2.0 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
@@ -11,6 +288,7 @@ Welcome to the v0.2.0 release of Kubernetes cloud controller manager for Proxmox
|
||||
|
||||
### Changelog
|
||||
|
||||
* d2da2e8 chore: release v0.2.0
|
||||
* 4e641a1 chore: bump deps
|
||||
* 591b88d chore: bump actions/checkout from 3 to 4
|
||||
* 45e3aeb chore: bump sigstore/cosign-installer from 3.1.1 to 3.1.2
|
||||
@@ -21,15 +299,15 @@ Welcome to the v0.2.0 release of Kubernetes cloud controller manager for Proxmox
|
||||
* dfd7c5f chore: bump deps
|
||||
* 38da18f ci: fix git tag
|
||||
* d8c6bed chore: bump deps
|
||||
* 6d79605 chore: release v0.1.1
|
||||
|
||||
<a name="v0.1.1"></a>
|
||||
## [v0.1.1](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.1.0...v0.1.1) (2023-05-08)
|
||||
## [v0.1.1](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/compare/v0.1.0...v0.1.1) (2023-05-12)
|
||||
|
||||
Welcome to the v0.1.1 release of Kubernetes cloud controller manager for Proxmox!
|
||||
|
||||
### Changelog
|
||||
|
||||
* 6d79605 chore: release v0.1.1
|
||||
* f8c32e1 test: cloud config
|
||||
* c051d38 ci: build trigger
|
||||
* a1e7cd0 chore: bump deps
|
||||
@@ -48,7 +326,6 @@ Welcome to the v0.1.0 release of Kubernetes cloud controller manager for Proxmox
|
||||
* b776e54 test: mock proxmox api
|
||||
* 641509b doc: helm chart readme
|
||||
* 90b66dc test: basic test
|
||||
* bf10985 chore: release v0.0.1
|
||||
|
||||
<a name="v0.0.1"></a>
|
||||
## v0.0.1 (2023-04-29)
|
||||
@@ -61,6 +338,7 @@ Welcome to the v0.0.1 release of Kubernetes cloud controller manager for Proxmox
|
||||
|
||||
### Changelog
|
||||
|
||||
* bf10985 chore: release v0.0.1
|
||||
* 0d89bf5 ci: add github checks
|
||||
* cc2dc17 refactor: proxmox cloud config
|
||||
* 850dcd4 chore: bump deps
|
||||
|
||||
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -1,5 +1,15 @@
|
||||
# Contributing
|
||||
|
||||
## Pull Requests
|
||||
|
||||
All PRs require a single commit.
|
||||
|
||||
Having one commit in a Pull Request is very important for several reasons:
|
||||
* A single commit per PR keeps the git history clean and readable.
|
||||
It helps reviewers and future developers understand the change as one atomic unit of work, instead of sifting through many intermediate or redundant commits.
|
||||
* One commit is easier to cherry-pick into another branch or to track in changelogs.
|
||||
* Squashing into one meaningful commit ensures the final PR only contains what matters.
|
||||
|
||||
## Developer Certificate of Origin
|
||||
|
||||
All commits require a [DCO](https://developercertificate.org/) sign-off.
|
||||
|
||||
10
Dockerfile
10
Dockerfile
@@ -1,12 +1,12 @@
|
||||
# syntax = docker/dockerfile:1.4
|
||||
# syntax = docker/dockerfile:1.18
|
||||
########################################
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} golang:1.20.7-alpine3.18 AS builder
|
||||
FROM --platform=${BUILDPLATFORM} golang:1.25.6-alpine AS builder
|
||||
RUN apk update && apk add --no-cache make
|
||||
ENV GO111MODULE on
|
||||
ENV GO111MODULE=on
|
||||
WORKDIR /src
|
||||
|
||||
COPY go.mod go.sum /src
|
||||
COPY ["go.mod", "go.sum", "/src/"]
|
||||
RUN go mod download && go mod verify
|
||||
|
||||
COPY . .
|
||||
@@ -22,7 +22,7 @@ LABEL org.opencontainers.image.source="https://github.com/sergelogvinov/proxmox-
|
||||
org.opencontainers.image.licenses="Apache-2.0" \
|
||||
org.opencontainers.image.description="Proxmox VE CCM for Kubernetes"
|
||||
|
||||
COPY --from=gcr.io/distroless/static-debian11:nonroot . .
|
||||
COPY --from=gcr.io/distroless/static-debian13:nonroot . .
|
||||
ARG TARGETARCH
|
||||
COPY --from=builder /src/bin/proxmox-cloud-controller-manager-${TARGETARCH} /bin/proxmox-cloud-controller-manager
|
||||
|
||||
|
||||
49
Makefile
49
Makefile
@@ -7,7 +7,7 @@ PLATFORM ?= linux/arm64,linux/amd64
|
||||
PUSH ?= false
|
||||
|
||||
VERSION ?= $(shell git describe --dirty --tag --match='v*')
|
||||
SHA ?= $(shell git describe --match=none --always --abbrev=8 --dirty)
|
||||
SHA ?= $(shell git describe --match=none --always --abbrev=7 --dirty)
|
||||
TAG ?= $(VERSION)
|
||||
|
||||
GO_LDFLAGS := -s -w
|
||||
@@ -22,6 +22,7 @@ TESTARGS ?= "-v"
|
||||
BUILD_ARGS := --platform=$(PLATFORM)
|
||||
ifeq ($(PUSH),true)
|
||||
BUILD_ARGS += --push=$(PUSH)
|
||||
BUILD_ARGS += --output type=image,annotation-index.org.opencontainers.image.source="https://github.com/$(USERNAME)/$(PROJECT)",annotation-index.org.opencontainers.image.description="Proxmox VE CCM for Kubernetes"
|
||||
else
|
||||
BUILD_ARGS += --output type=docker
|
||||
endif
|
||||
@@ -39,8 +40,8 @@ To build this project, you must have the following installed:
|
||||
|
||||
- git
|
||||
- make
|
||||
- golang 1.20+
|
||||
- golangci-lint
|
||||
- golang 1.24+
|
||||
- golangci-lint 2.2.0+
|
||||
|
||||
endef
|
||||
|
||||
@@ -70,16 +71,37 @@ build: ## Build
|
||||
.PHONY: run
|
||||
run: build ## Run
|
||||
./bin/proxmox-cloud-controller-manager-$(ARCH) --v=5 --kubeconfig=kubeconfig --cloud-config=proxmox-config.yaml --controllers=cloud-node,cloud-node-lifecycle \
|
||||
--use-service-account-credentials --leader-elect=false --bind-address=127.0.0.1
|
||||
--use-service-account-credentials --leader-elect=false --bind-address=127.0.0.1 --authorization-always-allow-paths=/healthz,/livez,/readyz,/metrics
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## Lint Code
|
||||
golangci-lint run --config .golangci.yml
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix: ## Fix Lint Issues
|
||||
golangci-lint run --fix --config .golangci.yml
|
||||
|
||||
.PHONY: unit
|
||||
unit: ## Unit Tests
|
||||
go test -tags=unit $(shell go list ./...) $(TESTARGS)
|
||||
|
||||
.PHONY: test
|
||||
test: lint unit ## Run all tests
|
||||
|
||||
.PHONY: licenses
|
||||
licenses:
|
||||
go-licenses check ./... --disallowed_types=forbidden,restricted,reciprocal,unknown
|
||||
|
||||
.PHONY: conformance
|
||||
conformance: ## Conformance
|
||||
docker run --rm -it -v $(PWD):/src -w /src ghcr.io/siderolabs/conform:v0.1.0-alpha.30 enforce
|
||||
|
||||
############
|
||||
|
||||
.PHONY: labels
|
||||
labels:
|
||||
@kubectl get nodes -o json | jq '.items[].metadata.labels'
|
||||
|
||||
############
|
||||
|
||||
.PHONY: helm-unit
|
||||
@@ -103,6 +125,7 @@ helm-release: ## Helm Release
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
yq -i '.appVersion = "$(TAG)"' charts/proxmox-cloud-controller-manager/Chart.yaml
|
||||
helm template -n kube-system proxmox-cloud-controller-manager \
|
||||
-f charts/proxmox-cloud-controller-manager/values.edge.yaml \
|
||||
--set-string image.tag=$(TAG) \
|
||||
@@ -111,6 +134,10 @@ docs:
|
||||
-f charts/proxmox-cloud-controller-manager/values.talos.yaml \
|
||||
--set-string image.tag=$(TAG) \
|
||||
charts/proxmox-cloud-controller-manager > docs/deploy/cloud-controller-manager-talos.yml
|
||||
helm template -n kube-system proxmox-cloud-controller-manager \
|
||||
--set-string image.tag=$(TAG) \
|
||||
--set useDaemonSet=true \
|
||||
charts/proxmox-cloud-controller-manager > docs/deploy/cloud-controller-manager-daemonset.yml
|
||||
helm-docs --sort-values-order=file charts/proxmox-cloud-controller-manager
|
||||
|
||||
release-update:
|
||||
@@ -122,12 +149,12 @@ release-update:
|
||||
#
|
||||
|
||||
docker-init:
|
||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
@docker run --rm --privileged multiarch/qemu-user-static -p yes ||:
|
||||
|
||||
docker context create multiarch ||:
|
||||
docker buildx create --name multiarch --driver docker-container --use ||:
|
||||
docker context use multiarch
|
||||
docker buildx inspect --bootstrap multiarch
|
||||
@docker context create multiarch ||:
|
||||
@docker buildx create --name multiarch --driver docker-container --use ||:
|
||||
@docker context use multiarch
|
||||
@docker buildx inspect --bootstrap multiarch
|
||||
|
||||
.PHONY: images
|
||||
images: ## Build images
|
||||
@@ -138,6 +165,10 @@ images: ## Build images
|
||||
-t $(IMAGE):$(TAG) \
|
||||
-f Dockerfile .
|
||||
|
||||
.PHONY: images-checks
|
||||
images-checks: images
|
||||
trivy image --exit-code 1 --ignore-unfixed --severity HIGH,CRITICAL --no-progress $(IMAGE):$(TAG)
|
||||
|
||||
.PHONY: images-cosign
|
||||
images-cosign:
|
||||
@cosign sign --yes $(COSING_ARGS) --recursive $(IMAGE):$(TAG)
|
||||
|
||||
109
README.md
109
README.md
@@ -12,8 +12,8 @@ Originally, it was designed to work with [Talos CCM](https://github.com/siderola
|
||||
The CCM does a few things: it initialises new nodes, applies common labels to them, and removes them when they're deleted. It also supports multiple clusters, meaning you can have one kubernetes cluster across multiple Proxmox clusters.
|
||||
|
||||
The basic definitions:
|
||||
* kubernetes `region` is a Proxmox cluster `clusters[].region`
|
||||
* kubernetes `zone` is a hypervisor host machine name
|
||||
* kubernetes label `topology.kubernetes.io/region` is a Proxmox cluster `clusters[].region`
|
||||
* kubernetes label `topology.kubernetes.io/zone` is a hypervisor host machine name
|
||||
|
||||
This makes it possible for me to use pods affinity/anti-affinity.
|
||||
|
||||
@@ -24,8 +24,10 @@ This makes it possible for me to use pods affinity/anti-affinity.
|
||||
clusters:
|
||||
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
# Proxox auth token
|
||||
token_id: "user!token-id"
|
||||
token_secret: "secret"
|
||||
# Uniq region name
|
||||
region: cluster-1
|
||||
- url: https://cluster-api-2.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
@@ -42,12 +44,25 @@ kind: Node
|
||||
metadata:
|
||||
labels:
|
||||
...
|
||||
# Type generated base on CPU and RAM
|
||||
node.kubernetes.io/instance-type: 2VCPU-2GB
|
||||
# Proxmox cluster name as in the config
|
||||
topology.kubernetes.io/region: cluster-1
|
||||
# Proxmox hypervisor host machine name
|
||||
topology.kubernetes.io/zone: pve-node-1
|
||||
|
||||
# Proxmox specific labels
|
||||
topology.proxmox.sinextra.dev/region: cluster-1
|
||||
topology.proxmox.sinextra.dev/zone: pve-node-1
|
||||
# HA group labels - the same idea as node-role
|
||||
group.topology.proxmox.sinextra.dev/${HAGroup}: ""
|
||||
|
||||
name: worker-1
|
||||
spec:
|
||||
...
|
||||
# providerID - magic string:
|
||||
# cluster-1 - cluster name as in the config
|
||||
# 123 - Proxmox VM ID
|
||||
providerID: proxmox://cluster-1/123
|
||||
status:
|
||||
addresses:
|
||||
@@ -57,84 +72,38 @@ status:
|
||||
type: Hostname
|
||||
```
|
||||
|
||||
# Install
|
||||
## Install
|
||||
|
||||
## Create a token
|
||||
See [Install](docs/install.md) for installation instructions.
|
||||
|
||||
Official [documentation](https://pve.proxmox.com/wiki/User_Management)
|
||||
## Controllers
|
||||
|
||||
```shell
|
||||
# Create role CCM
|
||||
pveum role add CCM -privs "VM.Audit"
|
||||
# Create user and grant permissions
|
||||
pveum user add kubernetes@pve
|
||||
pveum aclmod / -user kubernetes@pve -role CCM
|
||||
pveum user token add kubernetes@pve ccm -privsep 0
|
||||
```
|
||||
Support controllers:
|
||||
|
||||
## Deploy CCM
|
||||
* cloud-node
|
||||
* Updates node resource.
|
||||
* Assigns labels and taints based on Proxmox VM configuration.
|
||||
* cloud-node-lifecycle
|
||||
* Cleans up node resource when Proxmox VM is deleted.
|
||||
|
||||
Create the proxmox credentials
|
||||
## FAQ
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
clusters:
|
||||
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "kubernetes@pve!ccm"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
```
|
||||
|
||||
Upload it to the kubernetes:
|
||||
|
||||
```shell
|
||||
kubectl -n kube-system create secret generic proxmox-cloud-controller-manager --from-file=config.yaml
|
||||
```
|
||||
|
||||
### Method 1: kubectl
|
||||
|
||||
Deploy Proxmox CCM with `cloud-node,cloud-node-lifecycle` controllers
|
||||
|
||||
```shell
|
||||
kubectl apply -f https://raw.githubusercontent.com/sergelogvinov/proxmox-cloud-controller-manager/main/docs/deploy/cloud-controller-manager.yml
|
||||
```
|
||||
|
||||
Deploy Proxmox CCM with `cloud-node-lifecycle` controller (for Talos)
|
||||
|
||||
```shell
|
||||
kubectl apply -f https://raw.githubusercontent.com/sergelogvinov/proxmox-cloud-controller-manager/main/docs/deploy/cloud-controller-manager-talos.yml
|
||||
```
|
||||
|
||||
### Method 2: helm chart
|
||||
|
||||
Create the config file:
|
||||
|
||||
```yaml
|
||||
# proxmox-ccm.yaml
|
||||
config:
|
||||
clusters:
|
||||
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "kubernetes@pve!ccm"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
```
|
||||
|
||||
Deploy Proxmox CCM
|
||||
|
||||
```shell
|
||||
helm upgrade -i --namespace=kube-system -f proxmox-ccm.yaml \
|
||||
proxmox-cloud-controller-manager charts/proxmox-cloud-controller-manager
|
||||
```
|
||||
|
||||
More options you can find [here](charts/proxmox-cloud-controller-manager)
|
||||
See [FAQ](docs/faq.md) for answers to common questions.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcomed and appreciated!
|
||||
See [Contributing](CONTRIBUTING.md) for our guidelines.
|
||||
|
||||
If this project is useful to you, please consider starring the [repository](https://github.com/sergelogvinov/proxmox-cloud-controller-manager).
|
||||
|
||||
## Privacy Policy
|
||||
|
||||
This project does not collect or send any metrics or telemetry data.
|
||||
You can build the images yourself and store them in your private registry, see the [Makefile](Makefile) for details.
|
||||
|
||||
To provide feedback or report an issue, please use the [GitHub Issues](https://github.com/sergelogvinov/proxmox-cloud-controller-manager/issues).
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -148,3 +117,7 @@ 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.
|
||||
|
||||
---
|
||||
|
||||
`Proxmox®` is a registered trademark of [Proxmox Server Solutions GmbH](https://www.proxmox.com/en/about/company).
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
apiVersion: v2
|
||||
name: proxmox-cloud-controller-manager
|
||||
description: A Helm chart for Kubernetes
|
||||
description: Cloud Controller Manager plugin for Proxmox
|
||||
type: application
|
||||
home: https://github.com/sergelogvinov/proxmox-cloud-controller-manager
|
||||
icon: https://proxmox.com/templates/yoo_nano2/favicon.ico
|
||||
icon: https://raw.githubusercontent.com/sergelogvinov/proxmox-cloud-controller-manager/main/charts/proxmox-cloud-controller-manager/icon.png
|
||||
sources:
|
||||
- https://github.com/sergelogvinov/proxmox-cloud-controller-manager
|
||||
- https://github.com/sergelogvinov/proxmox-cloud-controller-manager
|
||||
keywords:
|
||||
- ccm
|
||||
- ccm
|
||||
- proxmox
|
||||
- kubernetes
|
||||
maintainers:
|
||||
- name: sergelogvinov
|
||||
url: https://github.com/sergelogvinov
|
||||
|
||||
- name: sergelogvinov
|
||||
url: https://github.com/sergelogvinov
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.6
|
||||
|
||||
version: 0.2.25
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: v0.2.0
|
||||
appVersion: v0.12.3
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
# proxmox-cloud-controller-manager
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
A Helm chart for Kubernetes
|
||||
Cloud Controller Manager plugin for Proxmox
|
||||
|
||||
The Cloud Controller Manager (CCM) is responsible for managing node resources in cloud-based Kubernetes environments.
|
||||
|
||||
Key functions of the Cloud Controller Manager:
|
||||
- `Node Management`: It manages nodes by initializing new nodes when they join the cluster (e.g., during scaling up) and removing nodes when they are no longer needed (e.g., during scaling down).
|
||||
- `Cloud-Specific Operations`: The CCM ensures that the cloud provider's API is integrated into the Kubernetes cluster to control and automate tasks like load balancing, storage provisioning, and node lifecycle management.
|
||||
|
||||
**Homepage:** <https://github.com/sergelogvinov/proxmox-cloud-controller-manager>
|
||||
|
||||
@@ -16,7 +22,22 @@ A Helm chart for Kubernetes
|
||||
|
||||
* <https://github.com/sergelogvinov/proxmox-cloud-controller-manager>
|
||||
|
||||
Example:
|
||||
## Requirements
|
||||
|
||||
You need to set `--cloud-provider=external` in the kubelet argument for all nodes in the cluster.
|
||||
|
||||
## Proxmox permissions
|
||||
|
||||
```shell
|
||||
# Create role CCM
|
||||
pveum role add CCM -privs "VM.Audit Sys.Audit"
|
||||
# Create user and grant permissions
|
||||
pveum user add kubernetes@pve
|
||||
pveum aclmod / -user kubernetes@pve -role CCM
|
||||
pveum user token add kubernetes@pve ccm -privsep 0
|
||||
```
|
||||
|
||||
## Helm values example
|
||||
|
||||
```yaml
|
||||
# proxmox-ccm.yaml
|
||||
@@ -35,18 +56,73 @@ enabledControllers:
|
||||
- cloud-node-lifecycle
|
||||
|
||||
# Deploy CCM only on control-plane nodes
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: ""
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
effect: NoSchedule
|
||||
```
|
||||
|
||||
## Example for credentials from separate Secrets
|
||||
```yaml
|
||||
# helm-values.yaml
|
||||
config:
|
||||
clusters:
|
||||
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
token_id_file: /run/secrets/cluster-1/token_id
|
||||
token_secret_file: /run/secrets/cluster-1/token_secret
|
||||
region: cluster-1
|
||||
- url: https://cluster-api-2.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
token_id_file: /run/secrets/cluster-2/token_id
|
||||
token_secret_file: /run/secrets/cluster-2/token_secret
|
||||
region: cluster-2
|
||||
extraVolumes:
|
||||
- name: credentials-cluster-1
|
||||
secret:
|
||||
secretName: proxmox-credentials-cluster-1
|
||||
- name: credentials-cluster-2
|
||||
secret:
|
||||
secretName: proxmox-credentials-cluster-2
|
||||
extraVolumeMounts:
|
||||
- name: credentials-cluster-1
|
||||
readOnly: true
|
||||
mountPath: "/run/secrets/cluster-1"
|
||||
- name: credentials-cluster-2
|
||||
readOnly: true
|
||||
mountPath: "/run/secrets/cluster-2"
|
||||
|
||||
```
|
||||
```yaml
|
||||
# secrets-proxmox-clusters.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: proxmox-credentials-cluster-1
|
||||
stringData:
|
||||
token_id: kubernetes@pve!csi
|
||||
token_secret: key1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: proxmox-credentials-cluster-2
|
||||
stringData:
|
||||
token_id: kubernetes@pve!csi
|
||||
token_secret: key2
|
||||
```
|
||||
|
||||
Deploy chart:
|
||||
|
||||
```shell
|
||||
helm upgrade -i --namespace=kube-system -f proxmox-ccm.yaml \
|
||||
proxmox-cloud-controller-manager charts/proxmox-cloud-controller-manager
|
||||
proxmox-cloud-controller-manager oci://ghcr.io/sergelogvinov/charts/proxmox-cloud-controller-manager
|
||||
```
|
||||
|
||||
## Values
|
||||
@@ -60,22 +136,25 @@ helm upgrade -i --namespace=kube-system -f proxmox-ccm.yaml \
|
||||
| imagePullSecrets | list | `[]` | |
|
||||
| nameOverride | string | `""` | |
|
||||
| fullnameOverride | string | `""` | |
|
||||
| extraEnvs | list | `[]` | Any extra environments for talos-cloud-controller-manager |
|
||||
| extraArgs | list | `[]` | Any extra arguments for talos-cloud-controller-manager |
|
||||
| enabledControllers | list | `["cloud-node","cloud-node-lifecycle"]` | List of controllers should be enabled. Use '*' to enable all controllers. Support only `cloud-node,cloud-node-lifecycle` controllers. |
|
||||
| logVerbosityLevel | int | `2` | Log verbosity level. See https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md for description of individual verbosity levels. |
|
||||
| existingConfigSecret | string | `nil` | Proxmox cluster config stored in secrets. |
|
||||
| existingConfigSecretKey | string | `"config.yaml"` | Proxmox cluster config stored in secrets key. |
|
||||
| config | object | `{"clusters":[]}` | Proxmox cluster config. |
|
||||
| config | object | `{"clusters":[],"features":{"provider":"default"}}` | Proxmox cluster config. refs: https://github.com/sergelogvinov/proxmox-cloud-controller-manager/blob/main/docs/config.md |
|
||||
| serviceAccount | object | `{"annotations":{},"create":true,"name":""}` | Pods Service Account. ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ |
|
||||
| priorityClassName | string | `"system-cluster-critical"` | CCM pods' priorityClassName. |
|
||||
| initContainers | list | `[]` | Add additional init containers to the CCM pods. ref: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ |
|
||||
| hostAliases | list | `[]` | hostAliases Deployment pod host aliases ref: https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/ |
|
||||
| podAnnotations | object | `{}` | Annotations for data pods. ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ |
|
||||
| podSecurityContext | object | `{"fsGroup":10258,"fsGroupChangePolicy":"OnRootMismatch","runAsGroup":10258,"runAsNonRoot":true,"runAsUser":10258}` | Pods Security Context. ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod |
|
||||
| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"seccompProfile":{"type":"RuntimeDefault"}}` | Container Security Context. ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod |
|
||||
| resources | object | `{"requests":{"cpu":"10m","memory":"32Mi"}}` | Resource requests and limits. ref: https://kubernetes.io/docs/user-guide/compute-resources/ |
|
||||
| updateStrategy | object | `{"rollingUpdate":{"maxUnavailable":1},"type":"RollingUpdate"}` | Deployment update stategy type. ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#updating-a-deployment |
|
||||
| useDaemonSet | bool | `false` | Deploy CCM in Daemonset mode. CCM will use hostNetwork. It allows to use CCM without CNI plugins. |
|
||||
| updateStrategy | object | `{"rollingUpdate":{"maxUnavailable":1},"type":"RollingUpdate"}` | Deployment update strategy type. ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#updating-a-deployment |
|
||||
| nodeSelector | object | `{}` | Node labels for data pods assignment. ref: https://kubernetes.io/docs/user-guide/node-selection/ |
|
||||
| tolerations | list | `[{"effect":"NoSchedule","key":"node-role.kubernetes.io/control-plane","operator":"Exists"},{"effect":"NoSchedule","key":"node.cloudprovider.kubernetes.io/uninitialized","operator":"Exists"}]` | Tolerations for data pods assignment. ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ |
|
||||
| affinity | object | `{}` | Affinity for data pods assignment. ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity |
|
||||
|
||||
----------------------------------------------
|
||||
Autogenerated from chart metadata using [helm-docs v1.11.2](https://github.com/norwoodj/helm-docs/releases/v1.11.2)
|
||||
| extraVolumes | list | `[]` | Additional volumes for Pods |
|
||||
| extraVolumeMounts | list | `[]` | Additional volume mounts for Pods |
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
|
||||
{{ template "chart.description" . }}
|
||||
|
||||
The Cloud Controller Manager (CCM) is responsible for managing node resources in cloud-based Kubernetes environments.
|
||||
|
||||
Key functions of the Cloud Controller Manager:
|
||||
- `Node Management`: It manages nodes by initializing new nodes when they join the cluster (e.g., during scaling up) and removing nodes when they are no longer needed (e.g., during scaling down).
|
||||
- `Cloud-Specific Operations`: The CCM ensures that the cloud provider's API is integrated into the Kubernetes cluster to control and automate tasks like load balancing, storage provisioning, and node lifecycle management.
|
||||
|
||||
{{ template "chart.homepageLine" . }}
|
||||
|
||||
{{ template "chart.maintainersSection" . }}
|
||||
@@ -14,7 +20,22 @@
|
||||
|
||||
{{ template "chart.requirementsSection" . }}
|
||||
|
||||
Example:
|
||||
## Requirements
|
||||
|
||||
You need to set `--cloud-provider=external` in the kubelet argument for all nodes in the cluster.
|
||||
|
||||
## Proxmox permissions
|
||||
|
||||
```shell
|
||||
# Create role CCM
|
||||
pveum role add CCM -privs "VM.Audit Sys.Audit"
|
||||
# Create user and grant permissions
|
||||
pveum user add kubernetes@pve
|
||||
pveum aclmod / -user kubernetes@pve -role CCM
|
||||
pveum user token add kubernetes@pve ccm -privsep 0
|
||||
```
|
||||
|
||||
## Helm values example
|
||||
|
||||
```yaml
|
||||
# proxmox-ccm.yaml
|
||||
@@ -33,20 +54,73 @@ enabledControllers:
|
||||
- cloud-node-lifecycle
|
||||
|
||||
# Deploy CCM only on control-plane nodes
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: ""
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
effect: NoSchedule
|
||||
```
|
||||
|
||||
## Example for credentials from separate Secrets
|
||||
```yaml
|
||||
# helm-values.yaml
|
||||
config:
|
||||
clusters:
|
||||
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
token_id_file: /run/secrets/cluster-1/token_id
|
||||
token_secret_file: /run/secrets/cluster-1/token_secret
|
||||
region: cluster-1
|
||||
- url: https://cluster-api-2.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
token_id_file: /run/secrets/cluster-2/token_id
|
||||
token_secret_file: /run/secrets/cluster-2/token_secret
|
||||
region: cluster-2
|
||||
extraVolumes:
|
||||
- name: credentials-cluster-1
|
||||
secret:
|
||||
secretName: proxmox-credentials-cluster-1
|
||||
- name: credentials-cluster-2
|
||||
secret:
|
||||
secretName: proxmox-credentials-cluster-2
|
||||
extraVolumeMounts:
|
||||
- name: credentials-cluster-1
|
||||
readOnly: true
|
||||
mountPath: "/run/secrets/cluster-1"
|
||||
- name: credentials-cluster-2
|
||||
readOnly: true
|
||||
mountPath: "/run/secrets/cluster-2"
|
||||
|
||||
```
|
||||
```yaml
|
||||
# secrets-proxmox-clusters.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: proxmox-credentials-cluster-1
|
||||
stringData:
|
||||
token_id: kubernetes@pve!csi
|
||||
token_secret: key1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: proxmox-credentials-cluster-2
|
||||
stringData:
|
||||
token_id: kubernetes@pve!csi
|
||||
token_secret: key2
|
||||
```
|
||||
|
||||
Deploy chart:
|
||||
|
||||
```shell
|
||||
helm upgrade -i --namespace=kube-system -f proxmox-ccm.yaml \
|
||||
proxmox-cloud-controller-manager charts/proxmox-cloud-controller-manager
|
||||
proxmox-cloud-controller-manager oci://ghcr.io/sergelogvinov/charts/proxmox-cloud-controller-manager
|
||||
```
|
||||
|
||||
{{ template "chart.valuesSection" . }}
|
||||
|
||||
{{ template "helm-docs.versionFooter" . }}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
|
||||
image:
|
||||
repository: ghcr.io/sergelogvinov/proxmox-cloud-controller-manager
|
||||
pullPolicy: Always
|
||||
tag: edge
|
||||
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: ""
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
|
||||
logVerbosityLevel: 4
|
||||
|
||||
extraEnvs:
|
||||
- name: KUBERNETES_SERVICE_HOST
|
||||
value: 127.0.0.1
|
||||
|
||||
enabledControllers:
|
||||
- cloud-node
|
||||
- cloud-node-lifecycle
|
||||
|
||||
BIN
charts/proxmox-cloud-controller-manager/icon.png
Normal file
BIN
charts/proxmox-cloud-controller-manager/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -1,14 +1,23 @@
|
||||
apiVersion: apps/v1
|
||||
{{- if .Values.useDaemonSet }}
|
||||
kind: DaemonSet
|
||||
{{- else }}
|
||||
kind: Deployment
|
||||
{{- end }}
|
||||
metadata:
|
||||
name: {{ include "proxmox-cloud-controller-manager.fullname" . }}
|
||||
labels:
|
||||
{{- include "proxmox-cloud-controller-manager.labels" . | nindent 4 }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
{{- if not .Values.useDaemonSet }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
strategy:
|
||||
type: {{ .Values.updateStrategy.type }}
|
||||
{{- else }}
|
||||
updateStrategy:
|
||||
type: {{ .Values.updateStrategy.type }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "proxmox-cloud-controller-manager.selectorLabels" . | nindent 6 }}
|
||||
@@ -35,6 +44,15 @@ spec:
|
||||
serviceAccountName: {{ include "proxmox-cloud-controller-manager.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
{{- if .Values.useDaemonSet }}
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
hostNetwork: true
|
||||
{{- end }}
|
||||
{{- with .Values.hostAliases }}
|
||||
hostAliases:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
initContainers: {{- toYaml .Values.initContainers | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
@@ -49,13 +67,26 @@ spec:
|
||||
- --leader-elect-resource-name=cloud-controller-manager-proxmox
|
||||
- --use-service-account-credentials
|
||||
- --secure-port=10258
|
||||
- --authorization-always-allow-paths=/healthz,/livez,/readyz,/metrics
|
||||
{{- with .Values.extraArgs }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
env:
|
||||
- name: SERVICE_ACCOUNT
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.serviceAccountName
|
||||
{{- with .Values.extraEnvs }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: metrics
|
||||
containerPort: 10258
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10258
|
||||
port: metrics
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
@@ -66,18 +97,36 @@ spec:
|
||||
- name: cloud-config
|
||||
mountPath: /etc/proxmox
|
||||
readOnly: true
|
||||
{{- with .Values.extraVolumeMounts }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- with .Values.affinity }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- else }}
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
{{- include "proxmox-cloud-controller-manager.selectorLabels" . | nindent 20 }}
|
||||
topologyKey: topology.kubernetes.io/zone
|
||||
weight: 1
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- with .Values.tolerations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.useDaemonSet }}
|
||||
- effect: NoSchedule
|
||||
key: node.kubernetes.io/not-ready
|
||||
operator: Exists
|
||||
{{- end }}
|
||||
{{- if not .Values.useDaemonSet }}
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
@@ -85,6 +134,7 @@ spec:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
{{- include "proxmox-cloud-controller-manager.selectorLabels" . | nindent 14 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
{{- if .Values.existingConfigSecret }}
|
||||
- name: cloud-config
|
||||
@@ -100,3 +150,6 @@ spec:
|
||||
secretName: {{ include "proxmox-cloud-controller-manager.fullname" . }}
|
||||
defaultMode: 416
|
||||
{{- end }}
|
||||
{{- with .Values.extraVolumes }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
|
||||
@@ -8,19 +8,19 @@ roleRef:
|
||||
name: system:{{ include "proxmox-cloud-controller-manager.fullname" . }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "proxmox-cloud-controller-manager.fullname" . }}
|
||||
name: {{ include "proxmox-cloud-controller-manager.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: system:{{ include "proxmox-cloud-controller-manager.fullname" . }}:extension-apiserver-authentication-reader
|
||||
namespace: {{ .Release.Namespace }}
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: extension-apiserver-authentication-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "proxmox-cloud-controller-manager.fullname" . }}
|
||||
name: {{ include "proxmox-cloud-controller-manager.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
|
||||
image:
|
||||
pullPolicy: Always
|
||||
tag: edge
|
||||
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: ""
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
|
||||
logVerbosityLevel: 4
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: ""
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
|
||||
logVerbosityLevel: 4
|
||||
|
||||
|
||||
@@ -16,8 +16,15 @@ imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
# -- Any extra environments for talos-cloud-controller-manager
|
||||
extraEnvs:
|
||||
[]
|
||||
# - name: KUBERNETES_SERVICE_HOST
|
||||
# value: 127.0.0.1
|
||||
|
||||
# -- Any extra arguments for talos-cloud-controller-manager
|
||||
extraArgs: []
|
||||
extraArgs:
|
||||
[]
|
||||
# - --cluster-name=kubernetes
|
||||
|
||||
# -- List of controllers should be enabled.
|
||||
@@ -39,7 +46,11 @@ existingConfigSecret: ~
|
||||
existingConfigSecretKey: config.yaml
|
||||
|
||||
# -- Proxmox cluster config.
|
||||
# refs: https://github.com/sergelogvinov/proxmox-cloud-controller-manager/blob/main/docs/config.md
|
||||
config:
|
||||
features:
|
||||
# Provider value can be "default" or "capmox"
|
||||
provider: "default"
|
||||
clusters: []
|
||||
# - url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||
# insecure: false
|
||||
@@ -61,6 +72,38 @@ serviceAccount:
|
||||
# -- CCM pods' priorityClassName.
|
||||
priorityClassName: system-cluster-critical
|
||||
|
||||
# -- Add additional init containers to the CCM pods.
|
||||
# ref: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
|
||||
initContainers:
|
||||
[]
|
||||
# - name: loadbalancer
|
||||
# restartPolicy: Always
|
||||
# image: ghcr.io/sergelogvinov/haproxy:2.8.3-alpine3.18
|
||||
# imagePullPolicy: IfNotPresent
|
||||
# env:
|
||||
# - name: SVC
|
||||
# value: "proxmox.domain.com"
|
||||
# - name: PORT
|
||||
# value: "8006"
|
||||
# securityContext:
|
||||
# runAsUser: 99
|
||||
# runAsGroup: 99
|
||||
# resources:
|
||||
# limits:
|
||||
# cpu: 50m
|
||||
# memory: 64Mi
|
||||
# requests:
|
||||
# cpu: 50m
|
||||
# memory: 32Mi
|
||||
|
||||
# -- hostAliases Deployment pod host aliases
|
||||
# ref: https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/
|
||||
hostAliases:
|
||||
[]
|
||||
# - ip: 127.0.0.1
|
||||
# hostnames:
|
||||
# - proxmox.domain.com
|
||||
|
||||
# -- Annotations for data pods.
|
||||
# ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
podAnnotations: {}
|
||||
@@ -80,7 +123,7 @@ securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
@@ -98,7 +141,12 @@ resources:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
|
||||
# -- Deployment update stategy type.
|
||||
# -- Deploy CCM in Daemonset mode.
|
||||
# CCM will use hostNetwork.
|
||||
# It allows to use CCM without CNI plugins.
|
||||
useDaemonSet: false
|
||||
|
||||
# -- Deployment update strategy type.
|
||||
# ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#updating-a-deployment
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
@@ -107,7 +155,8 @@ updateStrategy:
|
||||
|
||||
# -- Node labels for data pods assignment.
|
||||
# ref: https://kubernetes.io/docs/user-guide/node-selection/
|
||||
nodeSelector: {}
|
||||
nodeSelector:
|
||||
{}
|
||||
# node-role.kubernetes.io/control-plane: ""
|
||||
|
||||
# -- Tolerations for data pods assignment.
|
||||
@@ -123,3 +172,20 @@ tolerations:
|
||||
# -- Affinity for data pods assignment.
|
||||
# ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
|
||||
affinity: {}
|
||||
# nodeAffinity:
|
||||
# requiredDuringSchedulingIgnoredDuringExecution:
|
||||
# nodeSelectorTerms:
|
||||
# - matchExpressions:
|
||||
# - key: node-role.kubernetes.io/control-plane
|
||||
# operator: Exists
|
||||
|
||||
# -- Additional volumes for Pods
|
||||
extraVolumes: []
|
||||
# - name: ca
|
||||
# secret:
|
||||
# secretName: my-ca
|
||||
# -- Additional volume mounts for Pods
|
||||
extraVolumeMounts: []
|
||||
# - mountPath: /etc/ssl/certs/ca-certificates.crt
|
||||
# name: ca
|
||||
# subPath: ca.crt
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
Copyright 2023 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.
|
||||
@@ -44,7 +44,8 @@ import (
|
||||
func main() {
|
||||
ccmOptions, err := options.NewCloudControllerManagerOptions()
|
||||
if err != nil {
|
||||
klog.Fatalf("unable to initialize command options: %v", err)
|
||||
klog.ErrorS(err, "unable to initialize command options")
|
||||
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
|
||||
}
|
||||
|
||||
fss := cliflag.NamedFlagSets{}
|
||||
@@ -53,7 +54,8 @@ func main() {
|
||||
command.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
if flag.Name == "cloud-provider" {
|
||||
if err := flag.Value.Set(proxmox.ProviderName); err != nil {
|
||||
klog.Fatalf("unable to set cloud-provider flag value: %s", err)
|
||||
klog.ErrorS(err, "unable to set cloud-provider flag value")
|
||||
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -68,18 +70,21 @@ func cloudInitializer(config *config.CompletedConfig) cloudprovider.Interface {
|
||||
// initialize cloud provider with the cloud provider name and config file provided
|
||||
cloud, err := cloudprovider.InitCloudProvider(cloudConfig.Name, cloudConfig.CloudConfigFile)
|
||||
if err != nil {
|
||||
klog.Fatalf("Cloud provider could not be initialized: %v", err)
|
||||
klog.ErrorS(err, "Cloud provider could not be initialized")
|
||||
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
|
||||
}
|
||||
|
||||
if cloud == nil {
|
||||
klog.Fatalf("Cloud provider is nil")
|
||||
klog.InfoS("Cloud provider is nil")
|
||||
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
|
||||
}
|
||||
|
||||
if !cloud.HasClusterID() {
|
||||
if config.ComponentConfig.KubeCloudShared.AllowUntaggedCloud {
|
||||
klog.Warning("detected a cluster without a ClusterID. A ClusterID will be required in the future. Please tag your cluster to avoid any future issues")
|
||||
klog.InfoS("detected a cluster without a ClusterID. A ClusterID will be required in the future. Please tag your cluster to avoid any future issues")
|
||||
} else {
|
||||
klog.Fatalf("no ClusterID found. A ClusterID is required for the cloud provider to function properly. This check can be bypassed by setting the allow-untagged-cloud option")
|
||||
klog.InfoS("no ClusterID found. A ClusterID is required for the cloud provider to function properly. This check can be bypassed by setting the allow-untagged-cloud option")
|
||||
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
64
docs/config.md
Normal file
64
docs/config.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Cloud controller manager configuration file
|
||||
|
||||
This file is used to configure the Proxmox CCM.
|
||||
|
||||
```yaml
|
||||
features:
|
||||
# Provider type
|
||||
provider: default|capmox
|
||||
# Network mode
|
||||
network: default|qemu|auto
|
||||
# Enable or disable the IPv6 support
|
||||
ipv6_support_disabled: true|false
|
||||
# External IP address CIDRs list, comma-separated
|
||||
# Use `!` to exclude a CIDR
|
||||
external_ip_cidrs: '192.168.0.0/16,2001:db8:85a3::8a2e:370:7334/112,!fd00:1234:5678::/64'
|
||||
# IP addresses sort order, comma-separated
|
||||
# The IPs that do not match the CIDRs will be kept in the order they
|
||||
# were detected.
|
||||
ip_sort_order: '192.168.0.0/16,2001:db8:85a3::8a2e:370:7334/112'
|
||||
# Enable use of Proxmox HA group as a zone label
|
||||
ha_group: true|false
|
||||
|
||||
clusters:
|
||||
# List of Proxmox clusters
|
||||
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||
# Skip the certificate verification, if needed
|
||||
insecure: false
|
||||
# Proxmox api token
|
||||
token_id: "kubernetes-csi@pve!csi"
|
||||
token_secret: "secret"
|
||||
# (optional) Proxmox api token from separate file (s. Helm README.md)
|
||||
# token_id_file: /run/secrets/region-1/token_id
|
||||
# token_secret_file: /run/secrets/region-1/token_secret
|
||||
# Region name, which is cluster name
|
||||
region: Region-1
|
||||
|
||||
# Add more clusters if needed
|
||||
- url: https://cluster-api-2.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "kubernetes-csi@pve!csi"
|
||||
token_secret: "secret"
|
||||
region: Region-2
|
||||
```
|
||||
|
||||
## Cluster list
|
||||
|
||||
You can define multiple clusters in the `clusters` section.
|
||||
|
||||
* `url` - The URL of the Proxmox cluster API.
|
||||
* `insecure` - Set to `true` to skip TLS certificate verification.
|
||||
* `token_id` - The Proxmox API token ID.
|
||||
* `token_secret` - The name of the Kubernetes Secret that contains the Proxmox API token.
|
||||
* `region` - The name of the region, which is also used as `topology.kubernetes.io/region` label.
|
||||
|
||||
## Feature flags
|
||||
|
||||
* `provider` - Set the provider type. The default is `default`, which uses provider-id format `proxmox://<region>/<vm-id>`. The `capmox` value is used for working with the Cluster API for Proxmox (CAPMox), which uses provider-id format `proxmox://<SystemUUID>`.
|
||||
* `network` - Defines how the network addresses are handled by the CCM. The default value is `default`, which uses the kubelet argument `--node-ips` to assign IPs to the node resource. The `qemu` mode uses the QEMU agent API to retrieve network addresses from the virtual machine, while auto attempts to detect the best mode automatically.
|
||||
* `ipv6_support_disabled` - Set to `true` to ignore any IPv6 addresses. The default is `false`.
|
||||
* `external_ip_cidrs` - A comma-separated list of external IP address CIDRs. You can use `!` to exclude a CIDR from the list. This is useful for defining which IPs should be considered external and not included in the node addresses.
|
||||
* `ip_sort_order` - A comma-separated list defining the order in which IP addresses should be sorted. The IPs that do not match the CIDRs will be kept in the order they were detected.
|
||||
* `ha_group` - Set to `true` to enable the use of Proxmox HA group as a zone label. The default is `false`.
|
||||
|
||||
For more information about the network modes, see the [Networking documentation](networking.md).
|
||||
207
docs/deploy/cloud-controller-manager-daemonset.yml
Normal file
207
docs/deploy/cloud-controller-manager-daemonset.yml
Normal file
@@ -0,0 +1,207 @@
|
||||
---
|
||||
# Source: proxmox-cloud-controller-manager/templates/serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: proxmox-cloud-controller-manager
|
||||
labels:
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.2.23
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/version: "v0.12.3"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
namespace: kube-system
|
||||
---
|
||||
# Source: proxmox-cloud-controller-manager/templates/role.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: system:proxmox-cloud-controller-manager
|
||||
labels:
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.2.23
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/version: "v0.12.3"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
rules:
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- get
|
||||
- create
|
||||
- update
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes/status
|
||||
verbs:
|
||||
- patch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- serviceaccounts
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- serviceaccounts/token
|
||||
verbs:
|
||||
- create
|
||||
---
|
||||
# Source: proxmox-cloud-controller-manager/templates/rolebinding.yaml
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: system:proxmox-cloud-controller-manager
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: system:proxmox-cloud-controller-manager
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: proxmox-cloud-controller-manager
|
||||
namespace: kube-system
|
||||
---
|
||||
# Source: proxmox-cloud-controller-manager/templates/rolebinding.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: system:proxmox-cloud-controller-manager:extension-apiserver-authentication-reader
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: extension-apiserver-authentication-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: proxmox-cloud-controller-manager
|
||||
namespace: kube-system
|
||||
---
|
||||
# Source: proxmox-cloud-controller-manager/templates/deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: proxmox-cloud-controller-manager
|
||||
labels:
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.2.23
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/version: "v0.12.3"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
namespace: kube-system
|
||||
spec:
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: ce080eff0c26b50fe73bf9fcda017c8ad47c1000729fd0c555cfe3535c6d6222
|
||||
labels:
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
spec:
|
||||
enableServiceLinks: false
|
||||
priorityClassName: system-cluster-critical
|
||||
serviceAccountName: proxmox-cloud-controller-manager
|
||||
securityContext:
|
||||
fsGroup: 10258
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
runAsGroup: 10258
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10258
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
hostNetwork: true
|
||||
initContainers:
|
||||
[]
|
||||
containers:
|
||||
- name: proxmox-cloud-controller-manager
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
image: "ghcr.io/sergelogvinov/proxmox-cloud-controller-manager:v0.12.3"
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- --v=2
|
||||
- --cloud-provider=proxmox
|
||||
- --cloud-config=/etc/proxmox/config.yaml
|
||||
- --controllers=cloud-node,cloud-node-lifecycle
|
||||
- --leader-elect-resource-name=cloud-controller-manager-proxmox
|
||||
- --use-service-account-credentials
|
||||
- --secure-port=10258
|
||||
- --authorization-always-allow-paths=/healthz,/livez,/readyz,/metrics
|
||||
ports:
|
||||
- name: metrics
|
||||
containerPort: 10258
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: metrics
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
volumeMounts:
|
||||
- name: cloud-config
|
||||
mountPath: /etc/proxmox
|
||||
readOnly: true
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
topologyKey: topology.kubernetes.io/zone
|
||||
weight: 1
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
- effect: NoSchedule
|
||||
key: node.cloudprovider.kubernetes.io/uninitialized
|
||||
operator: Exists
|
||||
- effect: NoSchedule
|
||||
key: node.kubernetes.io/not-ready
|
||||
operator: Exists
|
||||
volumes:
|
||||
- name: cloud-config
|
||||
secret:
|
||||
secretName: proxmox-cloud-controller-manager
|
||||
defaultMode: 416
|
||||
@@ -5,10 +5,10 @@ kind: ServiceAccount
|
||||
metadata:
|
||||
name: proxmox-cloud-controller-manager
|
||||
labels:
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.1.6
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.2.23
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/version: "v0.2.0"
|
||||
app.kubernetes.io/version: "v0.12.3"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
namespace: kube-system
|
||||
---
|
||||
@@ -18,10 +18,10 @@ kind: ClusterRole
|
||||
metadata:
|
||||
name: system:proxmox-cloud-controller-manager
|
||||
labels:
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.1.6
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.2.23
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/version: "v0.2.0"
|
||||
app.kubernetes.io/version: "v0.12.3"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
rules:
|
||||
- apiGroups:
|
||||
@@ -106,10 +106,10 @@ kind: Deployment
|
||||
metadata:
|
||||
name: proxmox-cloud-controller-manager
|
||||
labels:
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.1.6
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.2.23
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/version: "v0.2.0"
|
||||
app.kubernetes.io/version: "v0.12.3"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
namespace: kube-system
|
||||
spec:
|
||||
@@ -123,7 +123,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: c69436cb1e16c36ff708b1003d3ca4c6ee6484d2524e2ba7d9b68f473acaa1ca
|
||||
checksum/config: ce080eff0c26b50fe73bf9fcda017c8ad47c1000729fd0c555cfe3535c6d6222
|
||||
labels:
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
@@ -137,6 +137,8 @@ spec:
|
||||
runAsGroup: 10258
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10258
|
||||
initContainers:
|
||||
[]
|
||||
containers:
|
||||
- name: proxmox-cloud-controller-manager
|
||||
securityContext:
|
||||
@@ -146,7 +148,7 @@ spec:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
image: "ghcr.io/sergelogvinov/proxmox-cloud-controller-manager:v0.2.0"
|
||||
image: "ghcr.io/sergelogvinov/proxmox-cloud-controller-manager:v0.12.3"
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- --v=4
|
||||
@@ -156,10 +158,15 @@ spec:
|
||||
- --leader-elect-resource-name=cloud-controller-manager-proxmox
|
||||
- --use-service-account-credentials
|
||||
- --secure-port=10258
|
||||
- --authorization-always-allow-paths=/healthz,/livez,/readyz,/metrics
|
||||
ports:
|
||||
- name: metrics
|
||||
containerPort: 10258
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10258
|
||||
port: metrics
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
@@ -172,8 +179,13 @@ spec:
|
||||
- name: cloud-config
|
||||
mountPath: /etc/proxmox
|
||||
readOnly: true
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: ""
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
key: node-role.kubernetes.io/control-plane
|
||||
|
||||
@@ -5,10 +5,10 @@ kind: ServiceAccount
|
||||
metadata:
|
||||
name: proxmox-cloud-controller-manager
|
||||
labels:
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.1.6
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.2.23
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/version: "v0.2.0"
|
||||
app.kubernetes.io/version: "v0.12.3"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
namespace: kube-system
|
||||
---
|
||||
@@ -18,10 +18,10 @@ kind: ClusterRole
|
||||
metadata:
|
||||
name: system:proxmox-cloud-controller-manager
|
||||
labels:
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.1.6
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.2.23
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/version: "v0.2.0"
|
||||
app.kubernetes.io/version: "v0.12.3"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
rules:
|
||||
- apiGroups:
|
||||
@@ -106,10 +106,10 @@ kind: Deployment
|
||||
metadata:
|
||||
name: proxmox-cloud-controller-manager
|
||||
labels:
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.1.6
|
||||
helm.sh/chart: proxmox-cloud-controller-manager-0.2.23
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/version: "v0.2.0"
|
||||
app.kubernetes.io/version: "v0.12.3"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
namespace: kube-system
|
||||
spec:
|
||||
@@ -123,7 +123,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: c69436cb1e16c36ff708b1003d3ca4c6ee6484d2524e2ba7d9b68f473acaa1ca
|
||||
checksum/config: ce080eff0c26b50fe73bf9fcda017c8ad47c1000729fd0c555cfe3535c6d6222
|
||||
labels:
|
||||
app.kubernetes.io/name: proxmox-cloud-controller-manager
|
||||
app.kubernetes.io/instance: proxmox-cloud-controller-manager
|
||||
@@ -137,6 +137,8 @@ spec:
|
||||
runAsGroup: 10258
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10258
|
||||
initContainers:
|
||||
[]
|
||||
containers:
|
||||
- name: proxmox-cloud-controller-manager
|
||||
securityContext:
|
||||
@@ -146,7 +148,7 @@ spec:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
image: "ghcr.io/sergelogvinov/proxmox-cloud-controller-manager:v0.2.0"
|
||||
image: "ghcr.io/sergelogvinov/proxmox-cloud-controller-manager:v0.12.3"
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- --v=4
|
||||
@@ -156,10 +158,15 @@ spec:
|
||||
- --leader-elect-resource-name=cloud-controller-manager-proxmox
|
||||
- --use-service-account-credentials
|
||||
- --secure-port=10258
|
||||
- --authorization-always-allow-paths=/healthz,/livez,/readyz,/metrics
|
||||
ports:
|
||||
- name: metrics
|
||||
containerPort: 10258
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10258
|
||||
port: metrics
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
@@ -172,8 +179,13 @@ spec:
|
||||
- name: cloud-config
|
||||
mountPath: /etc/proxmox
|
||||
readOnly: true
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: ""
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
key: node-role.kubernetes.io/control-plane
|
||||
|
||||
8
docs/faq.md
Normal file
8
docs/faq.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Fast answers to common questions
|
||||
|
||||
## Dose CCM support online VM migration?
|
||||
|
||||
No.
|
||||
Proxmox CCM uses [Cloud-Provider](https://github.com/kubernetes/cloud-provider.git) framework, which does not support label updates after the node initialization.
|
||||
|
||||
Kuernetes has node drain feature, which can be used to move pods from one node to another.
|
||||
248
docs/install.md
Normal file
248
docs/install.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# Install
|
||||
|
||||
Proxmox Cloud Controller Manager (CCM) supports controllers:
|
||||
* cloud-node
|
||||
* cloud-node-lifecycle
|
||||
|
||||
`cloud-node` - detects new node launched in the cluster and registers them in the cluster.
|
||||
Assigns labels and taints based on Proxmox VM configuration.
|
||||
|
||||
`cloud-node-lifecycle` - detects node deletion on Proxmox side and removes them from the cluster.
|
||||
|
||||
## Requirements
|
||||
|
||||
You need to set `--cloud-provider=external` in the kubelet argument for all nodes in the cluster.
|
||||
The flag informs the kubelet to offload cloud-specific responsibilities to this external component like Proxmox CCM.
|
||||
|
||||
```shell
|
||||
kubelet --cloud-provider=external
|
||||
```
|
||||
|
||||
Otherwise, kubelet will attempt to manage the node's lifecycle by itself, which can cause issues in environments using an external Cloud Controller Manager (CCM).
|
||||
|
||||
### Optional
|
||||
|
||||
```shell
|
||||
# ${IP} can be single or comma-separated list of two IPs (dual stack).
|
||||
kubelet --node-ip=${IP}
|
||||
```
|
||||
If your node has __multiple IP addresses__, you may need to set the `--node-ip` flag in the kubelet arguments to specify which IP address the kubelet should use.
|
||||
This ensures that the correct IP address is used for communication between the node and other components in the Kubernetes cluster, especially in environments where multiple network interfaces or IP addresses are present.
|
||||
|
||||
```shell
|
||||
# ${ID} has format proxmox://$REGION/$VMID.
|
||||
kubelet --provider-id=${ID}
|
||||
```
|
||||
If CCM cannot define VMID, you may need to set the `--provider-id` flag in the kubelet arguments to specify the VM ID in Proxmox. This ensures that the CCM can manage the node by VM ID.
|
||||
|
||||
```shell
|
||||
# ${NODENAME} is the name of the node.
|
||||
kubelet --hostname-override=${NODENAME}
|
||||
```
|
||||
If your node has a different hostname than the one registered in the cluster, you may need to set the `--hostname-override` flag in the kubelet arguments to specify the correct hostname.
|
||||
|
||||
|
||||
## Create a Proxmox token
|
||||
|
||||
Official [documentation](https://pve.proxmox.com/wiki/User_Management)
|
||||
|
||||
```shell
|
||||
# Create role CCM
|
||||
pveum role add CCM -privs "VM.Audit VM.GuestAgent.Audit Sys.Audit"
|
||||
# Create user and grant permissions
|
||||
pveum user add kubernetes@pve
|
||||
pveum aclmod / -user kubernetes@pve -role CCM
|
||||
pveum user token add kubernetes@pve ccm -privsep 0
|
||||
```
|
||||
|
||||
Or through terraform:
|
||||
|
||||
```hcl
|
||||
# Plugin: bpg/proxmox
|
||||
|
||||
resource "proxmox_virtual_environment_role" "ccm" {
|
||||
role_id = "CCM"
|
||||
|
||||
privileges = [
|
||||
"Sys.Audit",
|
||||
"VM.Audit",
|
||||
"VM.GuestAgent.Audit",
|
||||
]
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_user" "kubernetes" {
|
||||
acl {
|
||||
path = "/"
|
||||
propagate = true
|
||||
role_id = proxmox_virtual_environment_role.ccm.role_id
|
||||
}
|
||||
|
||||
comment = "Kubernetes"
|
||||
user_id = "kubernetes@pve"
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_user_token" "ccm" {
|
||||
comment = "Kubernetes CCM"
|
||||
token_name = "ccm"
|
||||
user_id = proxmox_virtual_environment_user.kubernetes.user_id
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_acl" "ccm" {
|
||||
token_id = proxmox_virtual_environment_user_token.ccm.id
|
||||
role_id = proxmox_virtual_environment_role.ccm.role_id
|
||||
|
||||
path = "/"
|
||||
propagate = true
|
||||
}
|
||||
```
|
||||
|
||||
## Deploy CCM
|
||||
|
||||
Create the proxmox credentials config file:
|
||||
|
||||
```yaml
|
||||
clusters:
|
||||
# List of Proxmox clusters, region mast be unique
|
||||
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "kubernetes@pve!ccm"
|
||||
# Token from the previous step
|
||||
token_secret: "secret"
|
||||
# Region name, can be any string, it will use as for kubernetes topology.kubernetes.io/region label
|
||||
region: cluster-1
|
||||
```
|
||||
|
||||
See [configuration documentation](config.md) for more details.
|
||||
|
||||
### Method 1: kubectl
|
||||
|
||||
Upload it to the kubernetes:
|
||||
|
||||
```shell
|
||||
kubectl -n kube-system create secret generic proxmox-cloud-controller-manager --from-file=config.yaml
|
||||
```
|
||||
|
||||
Deploy Proxmox CCM with `cloud-node,cloud-node-lifecycle` controllers
|
||||
|
||||
```shell
|
||||
kubectl apply -f https://raw.githubusercontent.com/sergelogvinov/proxmox-cloud-controller-manager/main/docs/deploy/cloud-controller-manager.yml
|
||||
```
|
||||
|
||||
Deploy Proxmox CCM with `cloud-node-lifecycle` controller (for Talos)
|
||||
|
||||
```shell
|
||||
kubectl apply -f https://raw.githubusercontent.com/sergelogvinov/proxmox-cloud-controller-manager/main/docs/deploy/cloud-controller-manager-talos.yml
|
||||
```
|
||||
|
||||
### Method 2: helm chart
|
||||
|
||||
Create the config file
|
||||
|
||||
```yaml
|
||||
# proxmox-ccm.yaml
|
||||
config:
|
||||
clusters:
|
||||
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "kubernetes@pve!ccm"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
```
|
||||
|
||||
Deploy Proxmox CCM (deployment mode)
|
||||
|
||||
```shell
|
||||
helm upgrade -i --namespace=kube-system -f proxmox-ccm.yaml \
|
||||
proxmox-cloud-controller-manager \
|
||||
oci://ghcr.io/sergelogvinov/charts/proxmox-cloud-controller-manager
|
||||
```
|
||||
|
||||
Deploy Proxmox CCM (daemonset mode)
|
||||
|
||||
It makes sense to deploy on all control-plane nodes. Do not forget to set the nodeSelector.
|
||||
|
||||
```shell
|
||||
helm upgrade -i --namespace=kube-system -f proxmox-ccm.yaml \
|
||||
--set useDaemonSet=true \
|
||||
proxmox-cloud-controller-manager \
|
||||
oci://ghcr.io/sergelogvinov/charts/proxmox-cloud-controller-manager
|
||||
```
|
||||
|
||||
More options you can find [here](charts/proxmox-cloud-controller-manager)
|
||||
|
||||
## Deploy CCM (Rancher)
|
||||
|
||||
Official [documentation](https://ranchermanager.docs.rancher.com/how-to-guides/new-user-guides/kubernetes-clusters-in-rancher-setup/node-requirements-for-rancher-managed-clusters)
|
||||
|
||||
Rancher RKE2 configuration:
|
||||
|
||||
```yaml
|
||||
machineGlobalConfig:
|
||||
# Kubelet predefined value --cloud-provider=external
|
||||
cloud-provider-name: external
|
||||
# Disable Rancher CCM
|
||||
disable-cloud-controller: true
|
||||
```
|
||||
|
||||
Create the helm values file:
|
||||
|
||||
```yaml
|
||||
# proxmox-ccm.yaml
|
||||
config:
|
||||
clusters:
|
||||
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "kubernetes@pve!ccm"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
|
||||
# Use host resolv.conf to resolve proxmox connection url
|
||||
useDaemonSet: true
|
||||
|
||||
# Set nodeSelector in daemonset mode is required
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: ""
|
||||
```
|
||||
|
||||
Deploy Proxmox CCM (daemondset mode)
|
||||
|
||||
```shell
|
||||
helm upgrade -i --namespace=kube-system -f proxmox-ccm.yaml \
|
||||
proxmox-cloud-controller-manager \
|
||||
oci://ghcr.io/sergelogvinov/charts/proxmox-cloud-controller-manager
|
||||
```
|
||||
|
||||
## Deploy CCM with load balancer (optional)
|
||||
|
||||
This optional setup to improve the Proxmox API availability.
|
||||
|
||||
See [load balancer](loadbalancer.md) for installation instructions.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
How `kubelet` works with flag `cloud-provider=external`:
|
||||
|
||||
1. kubelet join the cluster and send the `Node` object to the API server.
|
||||
Node object has values:
|
||||
* `node.cloudprovider.kubernetes.io/uninitialized` taint.
|
||||
* `alpha.kubernetes.io/provided-node-ip` annotation with the node IP.
|
||||
* `nodeInfo` field with system information.
|
||||
2. CCM detects the new node and sends a request to the Proxmox API to get the VM configuration. Like VMID, hostname, etc.
|
||||
3. CCM updates the `Node` object with labels, taints and `providerID` field. The `providerID` is immutable and has the format `proxmox://$REGION/$VMID`, it cannot be changed after the first update.
|
||||
4. CCM removes the `node.cloudprovider.kubernetes.io/uninitialized` taint.
|
||||
|
||||
If `kubelet` does not have `cloud-provider=external` flag, kubelet will expect that no external CCM is running and will try to manage the node lifecycle by itself.
|
||||
This can cause issues with Proxmox CCM.
|
||||
So, CCM will skip the node and will not update the `Node` object.
|
||||
|
||||
If you modify the `kubelet` flags, it's recommended to check all workloads in the cluster.
|
||||
Please __delete__ the node resource first, and __restart__ the kubelet.
|
||||
|
||||
The steps to troubleshoot the Proxmox CCM:
|
||||
1. scale down the CCM deployment to 1 replica.
|
||||
2. set log level to `--v=5` in the deployment.
|
||||
3. check the logs
|
||||
4. check kubelet flag `--cloud-provider=external`, delete the node resource and restart the kubelet.
|
||||
5. check the logs
|
||||
6. wait for 1 minute. If CCM cannot reach the Proxmox API, it will log the error.
|
||||
7. check tains, labels, and providerID in the `Node` object.
|
||||
82
docs/loadbalancer.md
Normal file
82
docs/loadbalancer.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Loadbalancer on top of the Proxmox cluster
|
||||
|
||||
Set up a load balancer to distribute traffic across multiple proxmox nodes.
|
||||
We use the [haproxy](https://hub.docker.com/_/haproxy) image to create a simple load balancer on top of the proxmox cluster.
|
||||
First, we need to create a headless service and set endpoints.
|
||||
|
||||
```yaml
|
||||
# proxmox-service.yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: proxmox
|
||||
namespace: kube-system
|
||||
spec:
|
||||
clusterIP: None
|
||||
ports:
|
||||
- name: https
|
||||
protocol: TCP
|
||||
port: 8006
|
||||
targetPort: 8006
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Endpoints
|
||||
metadata:
|
||||
name: proxmox
|
||||
namespace: kube-system
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 192.168.0.1
|
||||
- ip: 192.168.0.2
|
||||
ports:
|
||||
- port: 8006
|
||||
```
|
||||
|
||||
Apply the configuration to the cluster.
|
||||
|
||||
```bash
|
||||
kubectl apply -f proxmox-service.yaml
|
||||
```
|
||||
|
||||
Second, we need to deploy proxmox CCM with sidecar load balancer.
|
||||
Haproxy will resolve the `proxmox.kube-system.svc.cluster.local` service and uses IPs from the endpoints to distribute traffic.
|
||||
Proxmox CCM will use the `proxmox.domain.com` domain to connect to the proxmox cluster which is resolved to the load balancer IP (127.0.0.1).
|
||||
|
||||
```yaml
|
||||
# CCM helm chart values
|
||||
|
||||
config:
|
||||
clusters:
|
||||
- region: cluster
|
||||
url: https://proxmox.domain.com:8006/api2/json
|
||||
insecure: true
|
||||
token_id: kubernetes@pve!ccm
|
||||
token_secret: 11111111-1111-1111-1111-111111111111
|
||||
|
||||
hostAliases:
|
||||
- ip: 127.0.0.1
|
||||
hostnames:
|
||||
- proxmox.domain.com
|
||||
|
||||
initContainers:
|
||||
- name: loadbalancer
|
||||
restartPolicy: Always
|
||||
image: ghcr.io/sergelogvinov/haproxy:2.8.6-alpine3.19
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: SVC
|
||||
value: proxmox.kube-system.svc.cluster.local
|
||||
- name: PORT
|
||||
value: "8006"
|
||||
securityContext:
|
||||
runAsUser: 99
|
||||
runAsGroup: 99
|
||||
resources:
|
||||
limits:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 32Mi
|
||||
```
|
||||
47
docs/metrics.md
Normal file
47
docs/metrics.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Metrics documentation
|
||||
|
||||
This document is a reflection of the current state of the exposed metrics of the Proxmox CCM.
|
||||
|
||||
## Gather metrics
|
||||
|
||||
By default, the Proxmox CCM exposes metrics on the `https://localhost:10258/metrics` endpoint.
|
||||
|
||||
```yaml
|
||||
proxmox-cloud-controller-manager --authorization-always-allow-paths="/metrics" --secure-port=10258
|
||||
```
|
||||
|
||||
### Helm chart values
|
||||
|
||||
The following values can be set in the Helm chart to expose the metrics of the Talos CCM.
|
||||
|
||||
```yaml
|
||||
podAnnotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/scheme: "https"
|
||||
prometheus.io/port: "10258"
|
||||
```
|
||||
|
||||
## Metrics exposed by the CCM
|
||||
|
||||
### Proxmox API calls
|
||||
|
||||
|Metric name|Metric type|Labels/tags|
|
||||
|-----------|-----------|-----------|
|
||||
|proxmox_api_request_duration_seconds|Histogram|`request`=<api_request>|
|
||||
|proxmox_api_request_errors_total|Counter|`request`=<api_request>|
|
||||
|
||||
Example output:
|
||||
|
||||
```txt
|
||||
proxmox_api_request_duration_seconds_bucket{request="getVmInfo",le="0.1"} 13
|
||||
proxmox_api_request_duration_seconds_bucket{request="getVmInfo",le="0.25"} 172
|
||||
proxmox_api_request_duration_seconds_bucket{request="getVmInfo",le="0.5"} 199
|
||||
proxmox_api_request_duration_seconds_bucket{request="getVmInfo",le="1"} 210
|
||||
proxmox_api_request_duration_seconds_bucket{request="getVmInfo",le="2.5"} 210
|
||||
proxmox_api_request_duration_seconds_bucket{request="getVmInfo",le="5"} 210
|
||||
proxmox_api_request_duration_seconds_bucket{request="getVmInfo",le="10"} 210
|
||||
proxmox_api_request_duration_seconds_bucket{request="getVmInfo",le="30"} 210
|
||||
proxmox_api_request_duration_seconds_bucket{request="getVmInfo",le="+Inf"} 210
|
||||
proxmox_api_request_duration_seconds_sum{request="getVmInfo"} 39.698945394000006
|
||||
proxmox_api_request_duration_seconds_count{request="getVmInfo"} 210
|
||||
```
|
||||
69
docs/networking.md
Normal file
69
docs/networking.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Networking
|
||||
|
||||
## Node Addressing modes
|
||||
|
||||
There are three node addressing modes that Proxmox CCM supports:
|
||||
- Default mode (only mode available till v0.9.0)
|
||||
- Auto mode (available from vX.X.X)
|
||||
- QEMU-only Mode
|
||||
|
||||
In Default mode Proxmox CCM expects nodes to be provided with their private IP Address via the `--node-ip` kubelet flag. Default mode
|
||||
*does not* set the External IP of the node.
|
||||
|
||||
In Auto mode, Proxmox CCM makes use of both the host-networking access (if available) and the QEMU guest agent API (if available) to determine the available IP Addresses. At a minimum Auto mode will set only the Internal IP addresses of the node but can be configured to know which IP Addresses should be treated as external based on provided CIDRs and what order ALL IP addresses should be sorted in according to a sort order CIDR.
|
||||
|
||||
> [!NOTE]
|
||||
> All modes, including Default Mode, will use any IPs provided via the `alpha.kubernetes.io/provided-node-ip` annotation, unless they are part of the ignored cidrs list (non-default modes only).
|
||||
|
||||
### Default Mode
|
||||
|
||||
In Default Mode, Proxmox CCM assumes that the private IP of the node will be set using the kubelet arg `--node-ip`. Setting this flag adds an annotation to the node `alpha.kubernetes.io/provided-node-ip` which is used to then set the Node's `status.Addresses` field.
|
||||
|
||||
In this mode there is no validation of the IP address.
|
||||
|
||||
### Auto Mode
|
||||
|
||||
In Auto mode, Proxmox CCM uses access to the QEMU guest agent API (if available) to get a list of interfaces and IP Addresses as well as any IP addresses provided via `--node-ip`. From there depending on configuration it will setup all detected addresses as private and set any addresses matching a configured set of external CIDRs as external.
|
||||
|
||||
Enabling auto mode is done by setting the network feature mode to `auto`:
|
||||
|
||||
```yaml
|
||||
features:
|
||||
network:
|
||||
mode: auto
|
||||
```
|
||||
|
||||
### QEMU-only Mode
|
||||
|
||||
In QEMU Mode, Proxmox CCM uses the QEMU guest agent API to retrieve a list of IP addresses and set them as Node Addresses. Any node addresses provided via the `alpha.kubernetes.io/provided-node-ip` node annotation will also be available.
|
||||
|
||||
Enabling qemu-only mode is done by setting the network feature mode to `qemu`:
|
||||
|
||||
```yaml
|
||||
features:
|
||||
network:
|
||||
mode: qemu
|
||||
```
|
||||
|
||||
## Example configuration
|
||||
|
||||
The following is example configuration which sets IP addresses from 192.168.0.1 - 192.168.255.254 and 2001:0db8:85a3:0000:0000:8a2e:0370:0000 - 2001:0db8:85a3:0000:0000:8a2e:0370:ffff as "external" addresses. All other IPs from subnet 10.0.0.0/8 will be ignored.
|
||||
|
||||
To use any mode other than default specify the following configuration:
|
||||
|
||||
```yaml
|
||||
features:
|
||||
network:
|
||||
mode: auto
|
||||
external_ip_cidrs: '192.168.0.0/16,2001:db8:85a3::8a2e:370:7334/112,!10.0.0.0/8'
|
||||
```
|
||||
|
||||
Further configuration options are available as well. We can disable ipv6 support entirely and provide an order to sort IP addresses in (with any that don't match just being kept in whatever order the make it into the list):
|
||||
|
||||
```yaml
|
||||
features:
|
||||
network:
|
||||
mode: auto
|
||||
ipv6_support_disabled: true
|
||||
ip_sort_order: '192.168.0.0/16,2001:db8:85a3::8a2e:370:7334/112'
|
||||
```
|
||||
@@ -1,12 +1,20 @@
|
||||
# Make relese
|
||||
# Make release
|
||||
|
||||
## Change release version
|
||||
|
||||
```shell
|
||||
git checkout -b release-0.0.2
|
||||
git tag v0.0.2
|
||||
git commit --allow-empty -m "chore: release 2.0.0" -m "Release-As: 2.0.0"
|
||||
```
|
||||
|
||||
## Update helm chart and documentation
|
||||
|
||||
```shell
|
||||
git branch -D release-please--branches--main
|
||||
git checkout release-please--branches--main
|
||||
export `jq -r '"TAG=v"+.[]' hack/release-please-manifest.json`
|
||||
|
||||
make helm-unit docs
|
||||
make release-update
|
||||
|
||||
git add .
|
||||
git commit
|
||||
git commit -s --amend
|
||||
```
|
||||
|
||||
190
go.mod
190
go.mod
@@ -1,112 +1,124 @@
|
||||
module github.com/sergelogvinov/proxmox-cloud-controller-manager
|
||||
|
||||
go 1.20
|
||||
go 1.25.6
|
||||
|
||||
// replace github.com/sergelogvinov/go-proxmox => ../proxmox/go-proxmox
|
||||
// replace github.com/luthermonson/go-proxmox => github.com/sergelogvinov/go-proxmox-luthermonson v0.0.0-20251223032417-72ddd47a4a37
|
||||
|
||||
require (
|
||||
github.com/Telmate/proxmox-api-go v0.0.0-20230616173359-03f4e428f6c6
|
||||
github.com/jarcoal/httpmock v1.3.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/jarcoal/httpmock v1.4.1
|
||||
github.com/luthermonson/go-proxmox v0.3.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/samber/lo v1.52.0
|
||||
github.com/sergelogvinov/go-proxmox v0.1.0
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.uber.org/multierr v1.11.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.28.2
|
||||
k8s.io/apimachinery v0.28.2
|
||||
k8s.io/client-go v0.28.2
|
||||
k8s.io/cloud-provider v0.28.2
|
||||
k8s.io/component-base v0.28.2
|
||||
k8s.io/klog/v2 v2.100.1
|
||||
k8s.io/api v0.35.0
|
||||
k8s.io/apimachinery v0.35.0
|
||||
k8s.io/client-go v0.35.0
|
||||
k8s.io/cloud-provider v0.35.0
|
||||
k8s.io/component-base v0.35.0
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/buger/goterm v1.0.4 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/diskfs/go-diskfs v1.7.0 // indirect
|
||||
github.com/djherbis/times v1.6.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 // 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.3 // indirect
|
||||
github.com/google/cel-go v0.16.1 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/cel-go v0.26.1 // indirect
|
||||
github.com/google/gnostic-models v0.7.1 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.13.0 // indirect
|
||||
github.com/imdario/mergo v0.3.6 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jinzhu/copier v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/magefile/mage v1.15.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.1 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.16.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.9 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.9 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1 // indirect
|
||||
go.opentelemetry.io/otel v1.10.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/otel/sdk v1.10.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.10.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
||||
golang.org/x/net v0.13.0 // indirect
|
||||
golang.org/x/oauth2 v0.8.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
|
||||
google.golang.org/grpc v1.54.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/spf13/cobra v1.10.2 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.7 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.7 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.7 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
|
||||
google.golang.org/grpc v1.78.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/apiserver v0.28.2 // indirect
|
||||
k8s.io/component-helpers v0.28.2 // indirect
|
||||
k8s.io/controller-manager v0.28.2 // indirect
|
||||
k8s.io/kms v0.28.2 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
k8s.io/apiserver v0.35.0 // indirect
|
||||
k8s.io/component-helpers v0.35.0 // indirect
|
||||
k8s.io/controller-manager v0.35.0 // indirect
|
||||
k8s.io/kms v0.35.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect
|
||||
k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
843
go.sum
843
go.sum
@@ -1,666 +1,357 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/Telmate/proxmox-api-go v0.0.0-20230616173359-03f4e428f6c6 h1:GxLbCCxjuuJTVRq7FcYBN3Jif6/x2hZG4y+IdTp36Y8=
|
||||
github.com/Telmate/proxmox-api-go v0.0.0-20230616173359-03f4e428f6c6/go.mod h1:HKwnwBcgJxT+UjTUyRP7+aDxXSgu0kLWvlrRhd4i1YU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
|
||||
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
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/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/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-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
|
||||
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
|
||||
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/diskfs/go-diskfs v1.7.0 h1:vonWmt5CMowXwUc79jWyGrf2DIMeoOjkLlMnQYGVOs8=
|
||||
github.com/diskfs/go-diskfs v1.7.0/go.mod h1:LhQyXqOugWFRahYUSw47NyZJPezFzB9UELwhpszLP/k=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
|
||||
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/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-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY=
|
||||
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
||||
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
|
||||
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
|
||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
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/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/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/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/cel-go v0.16.1 h1:3hZfSNiAU3KOiNtxuFXVp5WFy4hf/Ly3Sa4/7F8SXNo=
|
||||
github.com/google/cel-go v0.16.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.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/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
|
||||
github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
|
||||
github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
|
||||
github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
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/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.13.0 h1:fi9bGIUJOGzzrHBbP8NWbTfNC5fKO6X7kFw40TOqGB8=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.13.0/go.mod h1:uY3Aurq+SxwQCpdX91xZ9CgxIMT1EsYtcidljXufYIY=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 h1:kEISI/Gx67NzH3nJxAmY/dGac80kKZgZt134u7Y/k1s=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4/go.mod h1:6Nz966r3vQYCqIzWsuEl9d7cf7mRhtDmm++sOxlnfxI=
|
||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
|
||||
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
|
||||
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
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/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/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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/luthermonson/go-proxmox v0.3.2 h1:/zUg6FCl9cAABx0xU3OIgtDtClY0gVXxOCsrceDNylc=
|
||||
github.com/luthermonson/go-proxmox v0.3.2/go.mod h1:oyFgg2WwTEIF0rP6ppjiixOHa5ebK1p8OaRiFhvICBQ=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI=
|
||||
github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
|
||||
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
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/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
|
||||
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/sergelogvinov/go-proxmox v0.1.0 h1:6S858CmCuC61x9SrfiuvKUanz2AJR+sdFHSZ+wI/GG8=
|
||||
github.com/sergelogvinov/go-proxmox v0.1.0/go.mod h1:3v8baTO3uoOuFKEWhYVjrh6ptEUQiAH/eHOYR06nDcU=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
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 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
|
||||
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk=
|
||||
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
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.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs=
|
||||
go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4=
|
||||
go.etcd.io/etcd/client/v2 v2.305.9 h1:YZ2OLi0OvR0H75AcgSUajjd5uqKDKocQUqROTG11jIo=
|
||||
go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E=
|
||||
go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA=
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.9 h1:6R2jg/aWd/zB9+9JxmijDKStGJAPFsX3e6BeJkMi6eQ=
|
||||
go.etcd.io/etcd/raft/v3 v3.5.9 h1:ZZ1GIHoUlHsn0QVqiRysAm3/81Xx7+i2d7nSdWxlOiI=
|
||||
go.etcd.io/etcd/server/v3 v3.5.9 h1:vomEmmxeztLtS5OEH7d0hBAg4cjVIu9wXuNzUZx2ZA0=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 h1:xFSRQBbXF6VvYRf2lqMJXxoB72XI1K/azav8TekHHSw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1 h1:sxoY9kG1s1WpSYNyzm24rlwH4lnRYFXUVVBmKMBfRgw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c=
|
||||
go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4=
|
||||
go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ=
|
||||
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.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY=
|
||||
go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE=
|
||||
go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E=
|
||||
go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM=
|
||||
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.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.etcd.io/etcd/api/v3 v3.6.7 h1:7BNJ2gQmc3DNM+9cRkv7KkGQDayElg8x3X+tFDYS+E0=
|
||||
go.etcd.io/etcd/api/v3 v3.6.7/go.mod h1:xJ81TLj9hxrYYEDmXTeKURMeY3qEDN24hqe+q7KhbnI=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.7 h1:vvzgyozz46q+TyeGBuFzVuI53/yd133CHceNb/AhBVs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.7/go.mod h1:2IVulJ3FZ/czIGl9T4lMF1uxzrhRahLqe+hSgy+Kh7Q=
|
||||
go.etcd.io/etcd/client/v3 v3.6.7 h1:9WqA5RpIBtdMxAy1ukXLAdtg2pAxNqW5NUoO2wQrE6U=
|
||||
go.etcd.io/etcd/client/v3 v3.6.7/go.mod h1:2XfROY56AXnUqGsvl+6k29wrwsSbEh1lAouQB1vHpeE=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU=
|
||||
go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0=
|
||||
go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0=
|
||||
go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
|
||||
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
|
||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
|
||||
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
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.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.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/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-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-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-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M=
|
||||
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||
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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw=
|
||||
k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg=
|
||||
k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ=
|
||||
k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU=
|
||||
k8s.io/apiserver v0.28.2 h1:rBeYkLvF94Nku9XfXyUIirsVzCzJBs6jMn3NWeHieyI=
|
||||
k8s.io/apiserver v0.28.2/go.mod h1:f7D5e8wH8MWcKD7azq6Csw9UN+CjdtXIVQUyUhrtb+E=
|
||||
k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY=
|
||||
k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY=
|
||||
k8s.io/cloud-provider v0.28.2 h1:9qsYm86hm4bnPgZbl9LE29Zfgjuq3NZR2dgtPioJ40s=
|
||||
k8s.io/cloud-provider v0.28.2/go.mod h1:40fqf6MtgYho5Eu4gkyLgh5abxU/QKTMTIwBxt4ILyU=
|
||||
k8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E=
|
||||
k8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc=
|
||||
k8s.io/component-helpers v0.28.2 h1:r/XJ265PMirW9EcGXr/F+2yWrLPo2I69KdvcY/h9HAo=
|
||||
k8s.io/component-helpers v0.28.2/go.mod h1:pF1R5YWQ+sgf0i6EbVm+MQCzkYuqutDUibdrkvAa6aI=
|
||||
k8s.io/controller-manager v0.28.2 h1:C2RKx+NH3Iw+4yLdTGNJlYUd4cRV1N8tKl4XfqMwuTk=
|
||||
k8s.io/controller-manager v0.28.2/go.mod h1:7bT6FlTE96Co7QevCtvcVnZZIJSaGj6F7EmyT2Rf3GY=
|
||||
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
|
||||
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kms v0.28.2 h1:KhG63LHopCdzs1oKA1j+NWleuIXudgOyCqJo4yi3GaM=
|
||||
k8s.io/kms v0.28.2/go.mod h1:iAjgIqBrV2+8kmsjbbgUkAyKSuYq5g1dW9knpt6OhaE=
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/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.1.2 h1:trsWhjU5jZrx6UvFu4WzQDrN7Pga4a7Qg+zcfcj64PA=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISXqCDVVcyO8hLn12AKVYYUjM7ftlqsqmrhMZE0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=
|
||||
k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=
|
||||
k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
|
||||
k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||
k8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4=
|
||||
k8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds=
|
||||
k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=
|
||||
k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=
|
||||
k8s.io/cloud-provider v0.35.0 h1:syiBCQbKh2gho/S1BkIl006Dc44pV8eAtGZmv5NMe7M=
|
||||
k8s.io/cloud-provider v0.35.0/go.mod h1:7grN+/Nt5Hf7tnSGPT3aErt4K7aQpygyCrGpbrQbzNc=
|
||||
k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94=
|
||||
k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0=
|
||||
k8s.io/component-helpers v0.35.0 h1:wcXv7HJRksgVjM4VlXJ1CNFBpyDHruRI99RrBtrJceA=
|
||||
k8s.io/component-helpers v0.35.0/go.mod h1:ahX0m/LTYmu7fL3W8zYiIwnQ/5gT28Ex4o2pymF63Co=
|
||||
k8s.io/controller-manager v0.35.0 h1:KteodmfVIRzfZ3RDaxhnHb72rswBxEngvdL9vuZOA9A=
|
||||
k8s.io/controller-manager v0.35.0/go.mod h1:1bVuPNUG6/dpWpevsJpXioS0E0SJnZ7I/Wqc9Awyzm4=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kms v0.35.0 h1:/x87FED2kDSo66csKtcYCEHsxF/DBlNl7LfJ1fVQs1o=
|
||||
k8s.io/kms v0.35.0/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=
|
||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ=
|
||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
|
||||
k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=
|
||||
k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
||||
35
hack/release-please-config.json
Normal file
35
hack/release-please-config.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
||||
"pull-request-header": ":robot: I have created a release",
|
||||
"pull-request-title-pattern": "chore: release v${version}",
|
||||
"group-pull-request-title-pattern": "chore: release v${version}",
|
||||
"packages": {
|
||||
".": {
|
||||
"changelog-path": "CHANGELOG.md",
|
||||
"release-type": "go",
|
||||
"skip-github-release": false,
|
||||
"bump-minor-pre-major": true,
|
||||
"include-v-in-tag": true,
|
||||
"draft": false,
|
||||
"draft-pull-request": true,
|
||||
"prerelease": false,
|
||||
"changelog-sections": [
|
||||
{
|
||||
"type": "feat",
|
||||
"section": "Features",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"type": "fix",
|
||||
"section": "Bug Fixes",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"type": "*",
|
||||
"section": "Changelog",
|
||||
"hidden": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
3
hack/release-please-manifest.json
Normal file
3
hack/release-please-manifest.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
".": "0.13.0"
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 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 cluster implements the multi-cloud provider interface for Proxmox.
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
pxapi "github.com/Telmate/proxmox-api-go/proxmox"
|
||||
)
|
||||
|
||||
// Cluster is a Proxmox client.
|
||||
type Cluster struct {
|
||||
config *ClustersConfig
|
||||
proxmox map[string]*pxapi.Client
|
||||
}
|
||||
|
||||
// NewCluster creates a new Proxmox cluster client.
|
||||
func NewCluster(config *ClustersConfig, hclient *http.Client) (*Cluster, error) {
|
||||
clusters := len(config.Clusters)
|
||||
if clusters > 0 {
|
||||
proxmox := make(map[string]*pxapi.Client, clusters)
|
||||
|
||||
for _, cfg := range config.Clusters {
|
||||
tlsconf := &tls.Config{InsecureSkipVerify: true}
|
||||
if !cfg.Insecure {
|
||||
tlsconf = nil
|
||||
}
|
||||
|
||||
client, err := pxapi.NewClient(cfg.URL, hclient, os.Getenv("PM_HTTP_HEADERS"), tlsconf, "", 600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.SetAPIToken(cfg.TokenID, cfg.TokenSecret)
|
||||
|
||||
proxmox[cfg.Region] = client
|
||||
}
|
||||
|
||||
return &Cluster{
|
||||
config: config,
|
||||
proxmox: proxmox,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no Proxmox clusters found")
|
||||
}
|
||||
|
||||
// CheckClusters checks if the Proxmox connection is working.
|
||||
func (c *Cluster) CheckClusters() error {
|
||||
for region, client := range c.proxmox {
|
||||
if _, err := client.GetVersion(); err != nil {
|
||||
return fmt.Errorf("failed to initialized proxmox client in region %s, error: %v", region, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProxmoxCluster returns a Proxmox cluster client in a given region.
|
||||
func (c *Cluster) GetProxmoxCluster(region string) (*pxapi.Client, error) {
|
||||
if c.proxmox[region] != nil {
|
||||
return c.proxmox[region], nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("proxmox cluster %s not found", region)
|
||||
}
|
||||
|
||||
// FindVMByName find a VM by name in all Proxmox clusters.
|
||||
func (c *Cluster) FindVMByName(name string) (*pxapi.VmRef, string, error) {
|
||||
for region, px := range c.proxmox {
|
||||
vmr, err := px.GetVmRefByName(name)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return vmr, region, nil
|
||||
}
|
||||
|
||||
return nil, "", fmt.Errorf("vm '%s' not found", name)
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 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 cluster_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/cluster"
|
||||
)
|
||||
|
||||
func newClusterEnv() (*cluster.ClustersConfig, error) {
|
||||
cfg, err := cluster.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
- url: https://127.0.0.1:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "user!token-id"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
- url: https://127.0.0.2:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "user!token-id"
|
||||
token_secret: "secret"
|
||||
region: cluster-2
|
||||
`))
|
||||
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
cfg, err := newClusterEnv()
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
client, err := cluster.NewCluster(&cluster.ClustersConfig{}, nil)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, client)
|
||||
|
||||
client, err = cluster.NewCluster(cfg, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
}
|
||||
|
||||
func TestCheckClusters(t *testing.T) {
|
||||
cfg, err := newClusterEnv()
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
client, err := cluster.NewCluster(cfg, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
|
||||
pxapi, err := client.GetProxmoxCluster("test")
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, pxapi)
|
||||
assert.Equal(t, "proxmox cluster test not found", err.Error())
|
||||
|
||||
pxapi, err = client.GetProxmoxCluster("cluster-1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, pxapi)
|
||||
|
||||
err = client.CheckClusters()
|
||||
assert.NotNil(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to initialized proxmox client in region")
|
||||
}
|
||||
|
||||
func TestFindVMByNameNonExist(t *testing.T) {
|
||||
cfg, err := newClusterEnv()
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
httpmock.RegisterResponder("GET", "https://127.0.0.1:8006/api2/json/cluster/resources",
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]interface{}{
|
||||
"data": []interface{}{
|
||||
map[string]interface{}{
|
||||
"node": "node-1",
|
||||
"type": "qemu",
|
||||
"vmid": 100,
|
||||
"name": "test1-vm",
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder("GET", "https://127.0.0.2:8006/api2/json/cluster/resources",
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]interface{}{
|
||||
"data": []interface{}{
|
||||
map[string]interface{}{
|
||||
"node": "node-2",
|
||||
"type": "qemu",
|
||||
"vmid": 100,
|
||||
"name": "test2-vm",
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
client, err := cluster.NewCluster(cfg, &http.Client{})
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
|
||||
vmr, cluster, err := client.FindVMByName("non-existing-vm")
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "", cluster)
|
||||
assert.Nil(t, vmr)
|
||||
assert.Contains(t, err.Error(), "vm 'non-existing-vm' not found")
|
||||
}
|
||||
|
||||
func TestFindVMByNameExist(t *testing.T) {
|
||||
cfg, err := newClusterEnv()
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
httpmock.RegisterResponder("GET", "https://127.0.0.1:8006/api2/json/cluster/resources",
|
||||
httpmock.NewJsonResponderOrPanic(200, map[string]interface{}{
|
||||
"data": []interface{}{
|
||||
map[string]interface{}{
|
||||
"node": "node-1",
|
||||
"type": "qemu",
|
||||
"vmid": 100,
|
||||
"name": "test1-vm",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder("GET", "https://127.0.0.2:8006/api2/json/cluster/resources",
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]interface{}{
|
||||
"data": []interface{}{
|
||||
map[string]interface{}{
|
||||
"node": "node-2",
|
||||
"type": "qemu",
|
||||
"vmid": 100,
|
||||
"name": "test2-vm",
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
client, err := cluster.NewCluster(cfg, &http.Client{})
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
vmName string
|
||||
expectedError error
|
||||
expectedVMID int
|
||||
expectedCluster string
|
||||
}{
|
||||
{
|
||||
msg: "vm not found",
|
||||
vmName: "non-existing-vm",
|
||||
expectedError: fmt.Errorf("vm 'non-existing-vm' not found"),
|
||||
},
|
||||
{
|
||||
msg: "Test1-VM",
|
||||
vmName: "test1-vm",
|
||||
expectedVMID: 100,
|
||||
expectedCluster: "cluster-1",
|
||||
},
|
||||
{
|
||||
msg: "Test2-VM",
|
||||
vmName: "test2-vm",
|
||||
expectedVMID: 100,
|
||||
expectedCluster: "cluster-2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
testCase := testCase
|
||||
|
||||
t.Run(fmt.Sprint(testCase.msg), func(t *testing.T) {
|
||||
vmr, cluster, err := client.FindVMByName(testCase.vmName)
|
||||
|
||||
if testCase.expectedError == nil {
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, vmr)
|
||||
assert.Equal(t, testCase.expectedVMID, vmr.VmId())
|
||||
assert.Equal(t, testCase.expectedCluster, cluster)
|
||||
} else {
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "", cluster)
|
||||
assert.Nil(t, vmr)
|
||||
assert.Contains(t, err.Error(), "vm 'non-existing-vm' not found")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 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 cluster
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// ClustersConfig is proxmox multi-cluster cloud config.
|
||||
type ClustersConfig struct {
|
||||
Clusters []struct {
|
||||
URL string `yaml:"url"`
|
||||
Insecure bool `yaml:"insecure,omitempty"`
|
||||
TokenID string `yaml:"token_id,omitempty"`
|
||||
TokenSecret string `yaml:"token_secret,omitempty"`
|
||||
Region string `yaml:"region,omitempty"`
|
||||
} `yaml:"clusters,omitempty"`
|
||||
}
|
||||
|
||||
// ReadCloudConfig reads cloud config from a reader.
|
||||
func ReadCloudConfig(config io.Reader) (ClustersConfig, error) {
|
||||
cfg := ClustersConfig{}
|
||||
|
||||
if config != nil {
|
||||
if err := yaml.NewDecoder(config).Decode(&cfg); err != nil {
|
||||
return ClustersConfig{}, err
|
||||
}
|
||||
}
|
||||
|
||||
for idx, c := range cfg.Clusters {
|
||||
if c.TokenID == "" {
|
||||
return ClustersConfig{}, fmt.Errorf("cluster #%d: token_id is required", idx+1)
|
||||
}
|
||||
|
||||
if c.TokenSecret == "" {
|
||||
return ClustersConfig{}, fmt.Errorf("cluster #%d: token_secret is required", idx+1)
|
||||
}
|
||||
|
||||
if c.Region == "" {
|
||||
return ClustersConfig{}, fmt.Errorf("cluster #%d: region is required", idx+1)
|
||||
}
|
||||
|
||||
if c.URL == "" || !strings.HasPrefix(c.URL, "http") {
|
||||
return ClustersConfig{}, fmt.Errorf("cluster #%d: url is required", idx+1)
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// ReadCloudConfigFromFile reads cloud config from a file.
|
||||
func ReadCloudConfigFromFile(file string) (ClustersConfig, error) {
|
||||
f, err := os.Open(filepath.Clean(file))
|
||||
if err != nil {
|
||||
return ClustersConfig{}, fmt.Errorf("error reading %s: %v", file, err)
|
||||
}
|
||||
defer f.Close() // nolint: errcheck
|
||||
|
||||
return ReadCloudConfig(f)
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 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 cluster_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/cluster"
|
||||
)
|
||||
|
||||
func TestReadCloudConfig(t *testing.T) {
|
||||
cfg, err := cluster.ReadCloudConfig(nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
// Empty config
|
||||
cfg, err = cluster.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
`))
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
// Wrong config
|
||||
cfg, err = cluster.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
test: false
|
||||
`))
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
// Non full config
|
||||
cfg, err = cluster.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
- url: abcd
|
||||
region: cluster-1
|
||||
`))
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
// Valid config with one cluster
|
||||
cfg, err = cluster.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
- url: https://example.com
|
||||
insecure: false
|
||||
token_id: "user!token-id"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
`))
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
assert.Equal(t, 1, len(cfg.Clusters))
|
||||
}
|
||||
|
||||
func TestReadCloudConfigFromFile(t *testing.T) {
|
||||
cfg, err := cluster.ReadCloudConfigFromFile("testdata/cloud-config.yaml")
|
||||
assert.NotNil(t, err)
|
||||
assert.EqualError(t, err, "error reading testdata/cloud-config.yaml: open testdata/cloud-config.yaml: no such file or directory")
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
cfg, err = cluster.ReadCloudConfigFromFile("../../hack/proxmox-config.yaml")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
assert.Equal(t, 2, len(cfg.Clusters))
|
||||
}
|
||||
158
pkg/config/config.go
Normal file
158
pkg/config/config.go
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
Copyright 2023 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 config is the configuration for the cloud provider.
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/proxmoxpool"
|
||||
)
|
||||
|
||||
// Provider specifies the provider. Can be 'default' or 'capmox'
|
||||
type Provider string
|
||||
|
||||
// ProviderDefault is the default provider
|
||||
const ProviderDefault Provider = "default"
|
||||
|
||||
// ProviderCapmox is the Provider for capmox
|
||||
const ProviderCapmox Provider = "capmox"
|
||||
|
||||
// NetworkMode specifies the network mode.
|
||||
type NetworkMode string
|
||||
|
||||
const (
|
||||
// NetworkModeDefault 'default' mode uses ips provided to the kubelet via --node-ip flags
|
||||
NetworkModeDefault NetworkMode = "default"
|
||||
// NetworkModeOnlyQemu 'qemu' mode tries to determine the ip addresses via the QEMU agent.
|
||||
NetworkModeOnlyQemu NetworkMode = "qemu"
|
||||
// NetworkModeAuto 'auto' attempts to use a combination of the above modes
|
||||
NetworkModeAuto NetworkMode = "auto"
|
||||
)
|
||||
|
||||
// ValidNetworkModes is a list of valid network modes.
|
||||
var ValidNetworkModes = []NetworkMode{NetworkModeDefault, NetworkModeOnlyQemu, NetworkModeAuto}
|
||||
|
||||
// NetworkOpts specifies the network options for the cloud provider.
|
||||
type NetworkOpts struct {
|
||||
ExternalIPCIDRS string `yaml:"external_ip_cidrs,omitempty"`
|
||||
IPv6SupportDisabled bool `yaml:"ipv6_support_disabled,omitempty"`
|
||||
IPSortOrder string `yaml:"ip_sort_order,omitempty"`
|
||||
Mode NetworkMode `yaml:"mode,omitempty"`
|
||||
}
|
||||
|
||||
// ClustersFeatures specifies the features for the cloud provider.
|
||||
type ClustersFeatures struct {
|
||||
// HAGroup specifies if the provider should use HA groups to determine node zone.
|
||||
// If enabled, the provider will use the HA group name as the zone name.
|
||||
// If disabled, the provider will use the node's cluster name as the zone name.
|
||||
// Default is false.
|
||||
HAGroup bool `yaml:"ha_group,omitempty"`
|
||||
// Provider specifies the provider to use. Can be 'default' or 'capmox'.
|
||||
// Default is 'default'.
|
||||
Provider Provider `yaml:"provider,omitempty"`
|
||||
// Network specifies the network options for the cloud provider.
|
||||
Network NetworkOpts `yaml:"network,omitempty"`
|
||||
// ForceUpdateLabels specifies if the provider should force update topology labels
|
||||
// topology.kubernetes.io/region and topology.kubernetes.io/zone on nodes when
|
||||
// a VM is migrated to a different zone within the Proxmox cluster.
|
||||
// Default is false.
|
||||
ForceUpdateLabels bool `yaml:"force_update_labels,omitempty"`
|
||||
}
|
||||
|
||||
// ClustersConfig is proxmox multi-cluster cloud config.
|
||||
type ClustersConfig struct {
|
||||
Features ClustersFeatures `yaml:"features,omitempty"`
|
||||
Clusters []*proxmoxpool.ProxmoxCluster `yaml:"clusters,omitempty"`
|
||||
}
|
||||
|
||||
// Errors for Reading Cloud Config
|
||||
var (
|
||||
ErrMissingPVERegion = errors.New("missing PVE region in cloud config")
|
||||
ErrMissingPVEAPIURL = errors.New("missing PVE API URL in cloud config")
|
||||
ErrAuthCredentialsMissing = errors.New("user, token or file credentials are required")
|
||||
ErrInvalidAuthCredentials = errors.New("must specify one of user, token or file credentials, not multiple")
|
||||
ErrInvalidCloudConfig = errors.New("invalid cloud config")
|
||||
ErrInvalidNetworkMode = fmt.Errorf("invalid network mode, valid modes are %v", ValidNetworkModes)
|
||||
)
|
||||
|
||||
// ReadCloudConfig reads cloud config from a reader.
|
||||
func ReadCloudConfig(config io.Reader) (ClustersConfig, error) {
|
||||
cfg := ClustersConfig{}
|
||||
|
||||
if config != nil {
|
||||
if err := yaml.NewDecoder(config).Decode(&cfg); err != nil {
|
||||
return ClustersConfig{}, errors.Join(ErrInvalidCloudConfig, err)
|
||||
}
|
||||
}
|
||||
|
||||
for idx, c := range cfg.Clusters {
|
||||
hasTokenAuth := c.TokenID != "" || c.TokenSecret != ""
|
||||
hasTokenFileAuth := c.TokenIDFile != "" || c.TokenSecretFile != ""
|
||||
|
||||
hasUserAuth := c.Username != "" && c.Password != ""
|
||||
if (hasTokenAuth && hasUserAuth) || (hasTokenFileAuth && hasUserAuth) || (hasTokenAuth && hasTokenFileAuth) {
|
||||
return ClustersConfig{}, fmt.Errorf("cluster #%d: %w", idx+1, ErrInvalidAuthCredentials)
|
||||
}
|
||||
|
||||
if !hasTokenAuth && !hasTokenFileAuth && !hasUserAuth {
|
||||
return ClustersConfig{}, fmt.Errorf("cluster #%d: %w", idx+1, ErrAuthCredentialsMissing)
|
||||
}
|
||||
|
||||
if c.Region == "" {
|
||||
return ClustersConfig{}, fmt.Errorf("cluster #%d: %w", idx+1, ErrMissingPVERegion)
|
||||
}
|
||||
|
||||
if c.URL == "" || !strings.HasPrefix(c.URL, "http") {
|
||||
return ClustersConfig{}, fmt.Errorf("cluster #%d: %w", idx+1, ErrMissingPVEAPIURL)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Features.Provider == "" {
|
||||
cfg.Features.Provider = ProviderDefault
|
||||
}
|
||||
|
||||
if cfg.Features.Network.Mode == "" {
|
||||
cfg.Features.Network.Mode = NetworkModeDefault
|
||||
}
|
||||
|
||||
// Validate network mode is valid
|
||||
if !slices.Contains(ValidNetworkModes, cfg.Features.Network.Mode) {
|
||||
return ClustersConfig{}, ErrInvalidNetworkMode
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// ReadCloudConfigFromFile reads cloud config from a file.
|
||||
func ReadCloudConfigFromFile(file string) (ClustersConfig, error) {
|
||||
f, err := os.Open(filepath.Clean(file))
|
||||
if err != nil {
|
||||
return ClustersConfig{}, fmt.Errorf("error reading %s: %v", file, err)
|
||||
}
|
||||
defer f.Close() // nolint: errcheck
|
||||
|
||||
return ReadCloudConfig(f)
|
||||
}
|
||||
233
pkg/config/config_test.go
Normal file
233
pkg/config/config_test.go
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
Copyright 2023 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 config_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
providerconfig "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/config"
|
||||
)
|
||||
|
||||
func TestReadCloudConfig(t *testing.T) {
|
||||
cfg, err := providerconfig.ReadCloudConfig(nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
// Empty config
|
||||
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
`))
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
// Wrong config
|
||||
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
test: false
|
||||
`))
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.ErrorIs(t, err, providerconfig.ErrInvalidCloudConfig)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
// Non full config
|
||||
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
- url: abcd
|
||||
region: cluster-1
|
||||
`))
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.ErrorIs(t, err, providerconfig.ErrAuthCredentialsMissing)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
// Valid config with one cluster and secret_file
|
||||
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
- url: https://example.com
|
||||
insecure: false
|
||||
token_id_file: "/etc/proxmox-secrets/cluster1/token_id"
|
||||
token_secret_file: "/etc/proxmox-secrets/cluster1/token_secret"
|
||||
region: cluster-1
|
||||
`))
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
assert.Equal(t, 1, len(cfg.Clusters))
|
||||
assert.Equal(t, "/etc/proxmox-secrets/cluster1/token_id", cfg.Clusters[0].TokenIDFile)
|
||||
|
||||
// Valid config with one cluster
|
||||
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
- url: https://example.com
|
||||
insecure: false
|
||||
token_id: "user!token-id"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
`))
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
assert.Equal(t, 1, len(cfg.Clusters))
|
||||
assert.Equal(t, "user!token-id", cfg.Clusters[0].TokenID)
|
||||
|
||||
// Valid config with one cluster (username/password), implicit default provider
|
||||
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
- url: https://example.com
|
||||
insecure: false
|
||||
username: "user@pam"
|
||||
password: "secret"
|
||||
region: cluster-1
|
||||
`))
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
assert.Equal(t, 1, len(cfg.Clusters))
|
||||
assert.Equal(t, providerconfig.ProviderDefault, cfg.Features.Provider)
|
||||
|
||||
// Valid config with one cluster (username/password), explicit provider default
|
||||
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
features:
|
||||
provider: 'default'
|
||||
clusters:
|
||||
- url: https://example.com
|
||||
insecure: false
|
||||
username: "user@pam"
|
||||
password: "secret"
|
||||
region: cluster-1
|
||||
`))
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
assert.Equal(t, 1, len(cfg.Clusters))
|
||||
assert.Equal(t, providerconfig.ProviderDefault, cfg.Features.Provider)
|
||||
|
||||
// Valid config with one cluster (username/password), explicit provider capmox
|
||||
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
features:
|
||||
provider: 'capmox'
|
||||
clusters:
|
||||
- url: https://example.com
|
||||
insecure: false
|
||||
username: "user@pam"
|
||||
password: "secret"
|
||||
region: cluster-1
|
||||
`))
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
assert.Equal(t, 1, len(cfg.Clusters))
|
||||
assert.Equal(t, providerconfig.ProviderCapmox, cfg.Features.Provider)
|
||||
|
||||
// Errors when token_id/token_secret are set with token_id_file/token_secret_file
|
||||
_, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
features:
|
||||
provider: 'capmox'
|
||||
clusters:
|
||||
- url: https://example.com
|
||||
insecure: false
|
||||
token_id_file: "/etc/proxmox-secrets/cluster1/token_id"
|
||||
token_secret_file: "/etc/proxmox-secrets/cluster1/token_secret"
|
||||
token_id: "ha"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
`))
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Errors when username/password are set with token_id/token_secret
|
||||
_, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
features:
|
||||
provider: 'capmox'
|
||||
clusters:
|
||||
- url: https://example.com
|
||||
insecure: false
|
||||
username: "user@pam"
|
||||
password: "secret"
|
||||
token_id: "ha"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
`))
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Errors when no region
|
||||
_, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
features:
|
||||
provider: 'capmox'
|
||||
clusters:
|
||||
- url: https://example.com
|
||||
insecure: false
|
||||
username: "user@pam"
|
||||
password: "secret"
|
||||
`))
|
||||
assert.NotNil(t, err)
|
||||
assert.ErrorIs(t, err, providerconfig.ErrMissingPVERegion)
|
||||
|
||||
// Errors when empty url
|
||||
_, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
features:
|
||||
provider: 'capmox'
|
||||
clusters:
|
||||
- url: ""
|
||||
region: test
|
||||
insecure: false
|
||||
username: "user@pam"
|
||||
password: "secret"
|
||||
`))
|
||||
assert.NotNil(t, err)
|
||||
assert.ErrorIs(t, err, providerconfig.ErrMissingPVEAPIURL)
|
||||
|
||||
// Errors when invalid url protocol
|
||||
_, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
features:
|
||||
provider: 'capmox'
|
||||
clusters:
|
||||
- url: quic://example.com
|
||||
insecure: false
|
||||
region: test
|
||||
username: "user@pam"
|
||||
password: "secret"
|
||||
`))
|
||||
assert.NotNil(t, err)
|
||||
assert.ErrorIs(t, err, providerconfig.ErrMissingPVEAPIURL)
|
||||
}
|
||||
|
||||
func TestNetworkConfig(t *testing.T) {
|
||||
// Empty config results in default network mode
|
||||
cfg, err := providerconfig.ReadCloudConfig(strings.NewReader(`---`))
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
assert.Equal(t, providerconfig.NetworkModeDefault, cfg.Features.Network.Mode)
|
||||
|
||||
// Invalid network mode value results in error
|
||||
_, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||
features:
|
||||
network:
|
||||
mode: 'invalid-mode'
|
||||
`))
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestReadCloudConfigFromFile(t *testing.T) {
|
||||
cfg, err := providerconfig.ReadCloudConfigFromFile("testdata/cloud-config.yaml")
|
||||
assert.NotNil(t, err)
|
||||
assert.EqualError(t, err, "error reading testdata/cloud-config.yaml: open testdata/cloud-config.yaml: no such file or directory")
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
cfg, err = providerconfig.ReadCloudConfigFromFile("../../hack/proxmox-config.yaml")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
assert.Equal(t, 2, len(cfg.Clusters))
|
||||
}
|
||||
36
pkg/metrics/metrics.go
Normal file
36
pkg/metrics/metrics.go
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2023 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 metrics collects metrics.
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// MetricContext indicates the context for Talos client metrics.
|
||||
type MetricContext struct {
|
||||
start time.Time
|
||||
attributes []string
|
||||
}
|
||||
|
||||
// NewMetricContext creates a new MetricContext.
|
||||
func NewMetricContext(resource string) *MetricContext {
|
||||
return &MetricContext{
|
||||
start: time.Now(),
|
||||
attributes: []string{resource},
|
||||
}
|
||||
}
|
||||
67
pkg/metrics/metrics_api.go
Normal file
67
pkg/metrics/metrics_api.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2023 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 metrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
// CSIMetrics contains the metrics for Talos API calls.
|
||||
type CSIMetrics struct {
|
||||
Duration *metrics.HistogramVec
|
||||
Errors *metrics.CounterVec
|
||||
}
|
||||
|
||||
var apiMetrics = registerAPIMetrics()
|
||||
|
||||
// ObserveRequest records the request latency and counts the errors.
|
||||
func (mc *MetricContext) ObserveRequest(err error) error {
|
||||
apiMetrics.Duration.WithLabelValues(mc.attributes...).Observe(
|
||||
time.Since(mc.start).Seconds())
|
||||
|
||||
if err != nil {
|
||||
apiMetrics.Errors.WithLabelValues(mc.attributes...).Inc()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func registerAPIMetrics() *CSIMetrics {
|
||||
m := &CSIMetrics{
|
||||
Duration: metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Name: "proxmox_api_request_duration_seconds",
|
||||
Help: "Latency of an Proxmox API call",
|
||||
Buckets: []float64{.1, .25, .5, 1, 2.5, 5, 10, 30},
|
||||
}, []string{"request"}),
|
||||
Errors: metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Name: "proxmox_api_request_errors_total",
|
||||
Help: "Total number of errors for an Proxmox API call",
|
||||
}, []string{"request"}),
|
||||
}
|
||||
|
||||
legacyregistry.MustRegister(
|
||||
m.Duration,
|
||||
m.Errors,
|
||||
)
|
||||
|
||||
return m
|
||||
}
|
||||
80
pkg/provider/provider.go
Normal file
80
pkg/provider/provider.go
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2023 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 provider implements the providerID interface for Proxmox.
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// ProviderName is the name of the Proxmox provider.
|
||||
ProviderName = "proxmox"
|
||||
)
|
||||
|
||||
var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `://([^/]*)/([^/]+)$`)
|
||||
|
||||
// GetProviderIDFromID returns the magic providerID for kubernetes node.
|
||||
func GetProviderIDFromID(region string, vmID int) string {
|
||||
return fmt.Sprintf("%s://%s/%d", ProviderName, region, vmID)
|
||||
}
|
||||
|
||||
// GetProviderIDFromUUID returns the magic providerID for kubernetes node.
|
||||
func GetProviderIDFromUUID(uuid string) string {
|
||||
return fmt.Sprintf("%s://%s", ProviderName, uuid)
|
||||
}
|
||||
|
||||
// GetVMID returns the VM ID from the providerID.
|
||||
func GetVMID(providerID string) (int, error) {
|
||||
if !strings.HasPrefix(providerID, ProviderName) {
|
||||
return 0, fmt.Errorf("foreign providerID or empty \"%s\"", providerID)
|
||||
}
|
||||
|
||||
matches := providerIDRegexp.FindStringSubmatch(providerID)
|
||||
if len(matches) != 3 {
|
||||
return 0, fmt.Errorf("providerID \"%s\" didn't match expected format \"%s://region/InstanceID\"", providerID, ProviderName)
|
||||
}
|
||||
|
||||
vmID, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("InstanceID have to be a number, but got \"%s\"", matches[2])
|
||||
}
|
||||
|
||||
return vmID, nil
|
||||
}
|
||||
|
||||
// ParseProviderID returns the VmRef and region from the providerID.
|
||||
func ParseProviderID(providerID string) (int, string, error) {
|
||||
if !strings.HasPrefix(providerID, ProviderName) {
|
||||
return 0, "", fmt.Errorf("foreign providerID or empty \"%s\"", providerID)
|
||||
}
|
||||
|
||||
matches := providerIDRegexp.FindStringSubmatch(providerID)
|
||||
if len(matches) != 3 {
|
||||
return 0, "", fmt.Errorf("providerID \"%s\" didn't match expected format \"%s://region/InstanceID\"", providerID, ProviderName)
|
||||
}
|
||||
|
||||
vmID, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return 0, "", fmt.Errorf("InstanceID have to be a number, but got \"%s\"", matches[2])
|
||||
}
|
||||
|
||||
return vmID, matches[1], nil
|
||||
}
|
||||
185
pkg/provider/provider_test.go
Normal file
185
pkg/provider/provider_test.go
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
Copyright 2023 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 provider_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
provider "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/provider"
|
||||
)
|
||||
|
||||
func TestGetProviderIDFromID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
region string
|
||||
vmID int
|
||||
expectedProviderID string
|
||||
}{
|
||||
{
|
||||
msg: "Valid providerID",
|
||||
region: "region",
|
||||
vmID: 123,
|
||||
expectedProviderID: "proxmox://region/123",
|
||||
},
|
||||
{
|
||||
msg: "No region",
|
||||
region: "",
|
||||
vmID: 123,
|
||||
expectedProviderID: "proxmox:///123",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(fmt.Sprint(testCase.msg), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
providerID := provider.GetProviderIDFromID(testCase.region, testCase.vmID)
|
||||
|
||||
assert.Equal(t, testCase.expectedProviderID, providerID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVmID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
providerID string
|
||||
expectedError error
|
||||
expectedvmID int
|
||||
}{
|
||||
{
|
||||
msg: "Valid VMID",
|
||||
providerID: "proxmox://region/123",
|
||||
expectedError: nil,
|
||||
expectedvmID: 123,
|
||||
},
|
||||
{
|
||||
msg: "Valid VMID with empty region",
|
||||
providerID: "proxmox:///123",
|
||||
expectedError: nil,
|
||||
expectedvmID: 123,
|
||||
},
|
||||
{
|
||||
msg: "Invalid providerID format",
|
||||
providerID: "proxmox://123",
|
||||
expectedError: fmt.Errorf("providerID \"proxmox://123\" didn't match expected format \"proxmox://region/InstanceID\""),
|
||||
},
|
||||
{
|
||||
msg: "Non proxmox providerID",
|
||||
providerID: "cloud:///123",
|
||||
expectedError: fmt.Errorf("foreign providerID or empty \"cloud:///123\""),
|
||||
expectedvmID: 123,
|
||||
},
|
||||
{
|
||||
msg: "Non proxmox providerID",
|
||||
providerID: "cloud://123",
|
||||
expectedError: fmt.Errorf("foreign providerID or empty \"cloud://123\""),
|
||||
expectedvmID: 123,
|
||||
},
|
||||
{
|
||||
msg: "InValid VMID",
|
||||
providerID: "proxmox://region/abc",
|
||||
expectedError: fmt.Errorf("InstanceID have to be a number, but got \"abc\""),
|
||||
expectedvmID: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(fmt.Sprint(testCase.msg), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
VMID, err := provider.GetVMID(testCase.providerID)
|
||||
|
||||
if testCase.expectedError != nil {
|
||||
assert.NotNil(t, err)
|
||||
assert.EqualError(t, err, testCase.expectedError.Error())
|
||||
} else {
|
||||
assert.Equal(t, testCase.expectedvmID, VMID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseProviderID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
providerID string
|
||||
expectedError error
|
||||
expectedvmID int
|
||||
expectedRegion string
|
||||
}{
|
||||
{
|
||||
msg: "Valid VMID",
|
||||
providerID: "proxmox://region/123",
|
||||
expectedError: nil,
|
||||
expectedvmID: 123,
|
||||
expectedRegion: "region",
|
||||
},
|
||||
{
|
||||
msg: "Valid VMID with empty region",
|
||||
providerID: "proxmox:///123",
|
||||
expectedError: nil,
|
||||
expectedvmID: 123,
|
||||
expectedRegion: "",
|
||||
},
|
||||
{
|
||||
msg: "Invalid providerID format",
|
||||
providerID: "proxmox://123",
|
||||
expectedError: fmt.Errorf("providerID \"proxmox://123\" didn't match expected format \"proxmox://region/InstanceID\""),
|
||||
},
|
||||
{
|
||||
msg: "Non proxmox providerID",
|
||||
providerID: "cloud:///123",
|
||||
expectedError: fmt.Errorf("foreign providerID or empty \"cloud:///123\""),
|
||||
},
|
||||
{
|
||||
msg: "Non proxmox providerID",
|
||||
providerID: "cloud://123",
|
||||
expectedError: fmt.Errorf("foreign providerID or empty \"cloud://123\""),
|
||||
},
|
||||
{
|
||||
msg: "InValid VMID",
|
||||
providerID: "proxmox://region/abc",
|
||||
expectedError: fmt.Errorf("InstanceID have to be a number, but got \"abc\""),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(fmt.Sprint(testCase.msg), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
vmr, region, err := provider.ParseProviderID(testCase.providerID)
|
||||
|
||||
if testCase.expectedError != nil {
|
||||
assert.NotNil(t, err)
|
||||
assert.EqualError(t, err, testCase.expectedError.Error())
|
||||
} else {
|
||||
assert.Equal(t, testCase.expectedvmID, vmr)
|
||||
assert.Equal(t, testCase.expectedRegion, region)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
22
pkg/proxmox/annotation.go
Normal file
22
pkg/proxmox/annotation.go
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright 2023 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 proxmox
|
||||
|
||||
const (
|
||||
// AnnotationProxmoxInstanceID is the annotation used to store the Proxmox node virtual machine ID.
|
||||
AnnotationProxmoxInstanceID = Group + "/instance-id"
|
||||
)
|
||||
@@ -1,11 +1,30 @@
|
||||
// Package proxmox is main CCM defenition.
|
||||
/*
|
||||
Copyright 2023 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 proxmox is main CCM definition.
|
||||
package proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/cluster"
|
||||
ccmConfig "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/config"
|
||||
provider "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/provider"
|
||||
pxpool "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/proxmoxpool"
|
||||
|
||||
clientkubernetes "k8s.io/client-go/kubernetes"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
@@ -14,25 +33,36 @@ import (
|
||||
|
||||
const (
|
||||
// ProviderName is the name of the Proxmox provider.
|
||||
ProviderName = "proxmox"
|
||||
ProviderName = provider.ProviderName
|
||||
|
||||
// ServiceAccountName is the service account name used in kube-system namespace.
|
||||
ServiceAccountName = "proxmox-cloud-controller-manager"
|
||||
ServiceAccountName = provider.ProviderName + "-cloud-controller-manager"
|
||||
// ServiceAccountNameEnv is the environment variable for the service account name.
|
||||
ServiceAccountNameEnv = "SERVICE_ACCOUNT"
|
||||
|
||||
// Group name
|
||||
Group = "proxmox.sinextra.dev"
|
||||
)
|
||||
|
||||
type cloud struct {
|
||||
client *cluster.Cluster
|
||||
kclient clientkubernetes.Interface
|
||||
client *client
|
||||
|
||||
instancesV2 cloudprovider.InstancesV2
|
||||
|
||||
ctx context.Context //nolint:containedctx
|
||||
stop func()
|
||||
}
|
||||
|
||||
type client struct {
|
||||
pxpool *pxpool.ProxmoxPool
|
||||
kclient clientkubernetes.Interface
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
|
||||
cfg, err := cluster.ReadCloudConfig(config)
|
||||
cloudprovider.RegisterCloudProvider(provider.ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
|
||||
cfg, err := ccmConfig.ReadCloudConfig(config)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to read config: %v", err)
|
||||
klog.ErrorS(err, "failed to read config")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
@@ -41,17 +71,27 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
func newCloud(config *cluster.ClustersConfig) (cloudprovider.Interface, error) {
|
||||
client, err := cluster.NewCluster(config, nil)
|
||||
func newCloud(config *ccmConfig.ClustersConfig) (cloudprovider.Interface, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
px, err := pxpool.NewProxmoxPool(config.Clusters)
|
||||
if err != nil {
|
||||
cancel()
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instancesInterface := newInstances(client)
|
||||
client := &client{
|
||||
pxpool: px,
|
||||
}
|
||||
|
||||
instancesInterface := newInstances(client, config.Features)
|
||||
|
||||
return &cloud{
|
||||
client: client,
|
||||
instancesV2: instancesInterface,
|
||||
ctx: ctx,
|
||||
stop: cancel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -59,28 +99,29 @@ func newCloud(config *cluster.ClustersConfig) (cloudprovider.Interface, error) {
|
||||
// to perform housekeeping or run custom controllers specific to the cloud provider.
|
||||
// Any tasks started here should be cleaned up when the stop channel closes.
|
||||
func (c *cloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) {
|
||||
c.kclient = clientBuilder.ClientOrDie(ServiceAccountName)
|
||||
serviceAccountName := os.Getenv(ServiceAccountNameEnv)
|
||||
if serviceAccountName == "" {
|
||||
serviceAccountName = ServiceAccountName
|
||||
}
|
||||
|
||||
klog.Infof("clientset initialized")
|
||||
c.client.kclient = clientBuilder.ClientOrDie(serviceAccountName)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c.ctx = ctx
|
||||
c.stop = cancel
|
||||
klog.InfoS("clientset initialized")
|
||||
|
||||
err := c.client.CheckClusters()
|
||||
err := c.client.pxpool.CheckClusters(c.ctx)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to check proxmox cluster: %v", err)
|
||||
klog.ErrorS(err, "failed to check proxmox cluster")
|
||||
}
|
||||
|
||||
// Broadcast the upstream stop signal to all provider-level goroutines
|
||||
// watching the provider's context for cancellation.
|
||||
go func(provider *cloud) {
|
||||
<-stop
|
||||
klog.V(3).Infof("received cloud provider termination signal")
|
||||
klog.V(3).InfoS("received cloud provider termination signal")
|
||||
provider.stop()
|
||||
}(c)
|
||||
|
||||
klog.Infof("proxmox initialized")
|
||||
klog.InfoS("proxmox initialized")
|
||||
}
|
||||
|
||||
// LoadBalancer returns a balancer interface.
|
||||
@@ -121,7 +162,7 @@ func (c *cloud) Routes() (cloudprovider.Routes, bool) {
|
||||
|
||||
// ProviderName returns the cloud provider ID.
|
||||
func (c *cloud) ProviderName() string {
|
||||
return ProviderName
|
||||
return provider.ProviderName
|
||||
}
|
||||
|
||||
// HasClusterID is not implemented.
|
||||
|
||||
@@ -22,18 +22,20 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/cluster"
|
||||
ccmConfig "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/config"
|
||||
provider "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/provider"
|
||||
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/proxmoxpool"
|
||||
)
|
||||
|
||||
func TestNewCloudError(t *testing.T) {
|
||||
cloud, err := newCloud(&cluster.ClustersConfig{})
|
||||
cloud, err := newCloud(&ccmConfig.ClustersConfig{})
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, cloud)
|
||||
assert.EqualError(t, err, "no Proxmox clusters found")
|
||||
assert.Equal(t, proxmoxpool.ErrClustersNotFound, err)
|
||||
}
|
||||
|
||||
func TestCloud(t *testing.T) {
|
||||
cfg, err := cluster.ReadCloudConfig(strings.NewReader(`
|
||||
cfg, err := ccmConfig.ReadCloudConfig(strings.NewReader(`
|
||||
clusters:
|
||||
- url: https://example.com
|
||||
insecure: false
|
||||
@@ -73,7 +75,7 @@ clusters:
|
||||
assert.Equal(t, res, false)
|
||||
|
||||
pName := cloud.ProviderName()
|
||||
assert.Equal(t, pName, ProviderName)
|
||||
assert.Equal(t, pName, provider.ProviderName)
|
||||
|
||||
clID := cloud.HasClusterID()
|
||||
assert.Equal(t, clID, true)
|
||||
|
||||
22
pkg/proxmox/errors.go
Normal file
22
pkg/proxmox/errors.go
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright 2023 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 proxmox
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
// ErrKubeletExternalProvider is returned when a kubelet node does not have --cloud-provider=external argument
|
||||
var ErrKubeletExternalProvider = errors.New("node does not have --cloud-provider=external argument")
|
||||
287
pkg/proxmox/instance_addresses.go
Normal file
287
pkg/proxmox/instance_addresses.go
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
Copyright 2023 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 proxmox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/luthermonson/go-proxmox"
|
||||
|
||||
providerconfig "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/config"
|
||||
metrics "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/metrics"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
cloudproviderapi "k8s.io/cloud-provider/api"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
noSortPriority = 0
|
||||
)
|
||||
|
||||
func (i *instances) addresses(ctx context.Context, node *v1.Node, info *instanceInfo) []v1.NodeAddress {
|
||||
var (
|
||||
providedIP string
|
||||
ok bool
|
||||
)
|
||||
|
||||
if providedIP, ok = node.ObjectMeta.Annotations[cloudproviderapi.AnnotationAlphaProvidedIPAddr]; !ok {
|
||||
klog.ErrorS(ErrKubeletExternalProvider, fmt.Sprintf(
|
||||
"instances.InstanceMetadata() called: annotation %s missing from node. Was kubelet started without --cloud-provider=external or --node-ip?",
|
||||
cloudproviderapi.AnnotationAlphaProvidedIPAddr),
|
||||
"node", klog.KRef("", node.Name))
|
||||
}
|
||||
|
||||
// providedIP is supposed to be a single IP but some kubelets might set a comma separated list of IPs.
|
||||
providedAddresses := []string{}
|
||||
if providedIP != "" {
|
||||
providedAddresses = strings.Split(providedIP, ",")
|
||||
}
|
||||
|
||||
addresses := []v1.NodeAddress{
|
||||
{Type: v1.NodeHostName, Address: node.Name},
|
||||
}
|
||||
|
||||
for _, address := range providedAddresses {
|
||||
if address = strings.TrimSpace(address); address != "" {
|
||||
parsedAddress := net.ParseIP(address)
|
||||
if parsedAddress != nil {
|
||||
addresses = append(addresses, v1.NodeAddress{
|
||||
Type: v1.NodeInternalIP,
|
||||
Address: parsedAddress.String(),
|
||||
})
|
||||
} else {
|
||||
klog.Warningf("Ignoring invalid provided address '%s' for node %s", address, node.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i.networkOpts.Mode == providerconfig.NetworkModeDefault {
|
||||
klog.V(4).InfoS("instances.addresses() returning provided IPs", "node", klog.KObj(node))
|
||||
|
||||
return addresses
|
||||
}
|
||||
|
||||
if i.networkOpts.Mode == providerconfig.NetworkModeOnlyQemu || i.networkOpts.Mode == providerconfig.NetworkModeAuto {
|
||||
newAddresses, err := i.retrieveQemuAddresses(ctx, info)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Failed to retrieve host addresses")
|
||||
}
|
||||
|
||||
addToNodeAddresses(&addresses, newAddresses...)
|
||||
}
|
||||
|
||||
// Remove addresses that match the ignored CIDRs
|
||||
if len(i.networkOpts.IgnoredCIDRs) > 0 {
|
||||
var removableAddresses []v1.NodeAddress
|
||||
|
||||
for _, addr := range addresses {
|
||||
ip := net.ParseIP(addr.Address)
|
||||
if ip != nil && isAddressInCIDRList(i.networkOpts.IgnoredCIDRs, ip) {
|
||||
removableAddresses = append(removableAddresses, addr)
|
||||
}
|
||||
}
|
||||
|
||||
removeFromNodeAddresses(&addresses, removableAddresses...)
|
||||
}
|
||||
|
||||
sortNodeAddresses(addresses, i.networkOpts.SortOrder)
|
||||
|
||||
klog.V(4).InfoS("instances.addresses() returning addresses", "addresses", addresses, "node", klog.KObj(node))
|
||||
|
||||
return addresses
|
||||
}
|
||||
|
||||
// retrieveQemuAddresses retrieves the addresses from the QEMU agent
|
||||
func (i *instances) retrieveQemuAddresses(ctx context.Context, info *instanceInfo) ([]v1.NodeAddress, error) {
|
||||
var addresses []v1.NodeAddress
|
||||
|
||||
nics, err := i.getInstanceNics(ctx, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, nic := range nics {
|
||||
if slices.Contains([]string{"lo", "cilium_net", "cilium_host"}, nic.Name) ||
|
||||
strings.HasPrefix(nic.Name, "dummy") {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ip := range nic.IPAddresses {
|
||||
i.processIP(ctx, &addresses, ip.IPAddress)
|
||||
}
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
func (i *instances) processIP(_ context.Context, addresses *[]v1.NodeAddress, addr string) {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil || ip.IsLoopback() {
|
||||
return
|
||||
}
|
||||
|
||||
if ip.To4() == nil {
|
||||
if i.networkOpts.IPv6SupportDisabled {
|
||||
klog.V(4).InfoS("Skipping IPv6 address due to IPv6 support being disabled", "address", ip.String())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if ip.IsPrivate() || ip.IsLinkLocalUnicast() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
addressType := v1.NodeInternalIP
|
||||
if len(i.networkOpts.ExternalCIDRs) != 0 && isAddressInCIDRList(i.networkOpts.ExternalCIDRs, ip) {
|
||||
addressType = v1.NodeExternalIP
|
||||
}
|
||||
|
||||
*addresses = append(*addresses, v1.NodeAddress{
|
||||
Type: addressType,
|
||||
Address: ip.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func (i *instances) getInstanceNics(ctx context.Context, info *instanceInfo) ([]*proxmox.AgentNetworkIface, error) {
|
||||
result := make([]*proxmox.AgentNetworkIface, 0)
|
||||
|
||||
px, err := i.c.pxpool.GetProxmoxCluster(info.Region)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
vm, err := px.GetVMConfig(ctx, info.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mc := metrics.NewMetricContext("getVmInfo")
|
||||
|
||||
nicset, err := vm.AgentGetNetworkIFaces(ctx)
|
||||
if mc.ObserveRequest(err) != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
klog.V(4).InfoS("getInstanceNics() retrieved IP set", "nicset", nicset)
|
||||
|
||||
return nicset, nil
|
||||
}
|
||||
|
||||
// getSortPriority returns the priority as int of an address.
|
||||
//
|
||||
// The priority depends on the index of the CIDR in the list the address is matching,
|
||||
// where the first item of the list has higher priority than the last.
|
||||
//
|
||||
// If the address does not match any CIDR or is not an IP address the function returns noSortPriority.
|
||||
func getSortPriority(list []*net.IPNet, address string) int {
|
||||
parsedAddress := net.ParseIP(address)
|
||||
if parsedAddress == nil {
|
||||
return noSortPriority
|
||||
}
|
||||
|
||||
for i, cidr := range list {
|
||||
if cidr.Contains(parsedAddress) {
|
||||
return len(list) - i
|
||||
}
|
||||
}
|
||||
|
||||
return noSortPriority
|
||||
}
|
||||
|
||||
// sortNodeAddresses sorts node addresses based on comma separated list of CIDRs represented by addressSortOrder.
|
||||
//
|
||||
// The function only sorts addresses which match the CIDR and leaves the other addresses in the same order they are in.
|
||||
// Essentially, it will also group the addresses matching a CIDR together and sort them ascending in this group,
|
||||
// whereas the inter-group sorting depends on the priority.
|
||||
//
|
||||
// The priority depends on the order of the item in addressSortOrder, where the first item has higher priority than the last.
|
||||
func sortNodeAddresses(addresses []v1.NodeAddress, addressSortOrder []*net.IPNet) {
|
||||
sort.SliceStable(addresses, func(i int, j int) bool {
|
||||
addressLeft := addresses[i]
|
||||
addressRight := addresses[j]
|
||||
|
||||
priorityLeft := getSortPriority(addressSortOrder, addressLeft.Address)
|
||||
priorityRight := getSortPriority(addressSortOrder, addressRight.Address)
|
||||
|
||||
// ignore priorities of value 0 since this means the address has noSortPriority and we need to sort by priority
|
||||
if priorityLeft > noSortPriority && priorityLeft == priorityRight {
|
||||
return bytes.Compare(net.ParseIP(addressLeft.Address), net.ParseIP(addressRight.Address)) < 0
|
||||
}
|
||||
|
||||
return priorityLeft > priorityRight
|
||||
})
|
||||
}
|
||||
|
||||
// addToNodeAddresses appends the NodeAddresses to the passed-by-pointer slice,
|
||||
// only if they do not already exist
|
||||
func addToNodeAddresses(addresses *[]v1.NodeAddress, addAddresses ...v1.NodeAddress) {
|
||||
for _, add := range addAddresses {
|
||||
exists := false
|
||||
|
||||
for _, existing := range *addresses {
|
||||
if existing.Address == add.Address && existing.Type == add.Type {
|
||||
exists = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
*addresses = append(*addresses, add)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeFromNodeAddresses removes the NodeAddresses from the passed-by-pointer
|
||||
// slice if they already exist.
|
||||
func removeFromNodeAddresses(addresses *[]v1.NodeAddress, removeAddresses ...v1.NodeAddress) {
|
||||
var indexesToRemove []int
|
||||
|
||||
for _, remove := range removeAddresses {
|
||||
for i := len(*addresses) - 1; i >= 0; i-- {
|
||||
existing := (*addresses)[i]
|
||||
if existing.Address == remove.Address && (existing.Type == remove.Type || remove.Type == "") {
|
||||
indexesToRemove = append(indexesToRemove, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range indexesToRemove {
|
||||
if i < len(*addresses) {
|
||||
*addresses = append((*addresses)[:i], (*addresses)[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isAddressInCIDRList checks if the given address is contained in any of the CIDRs in the list.
|
||||
func isAddressInCIDRList(cidrs []*net.IPNet, address net.IP) bool {
|
||||
for _, cidr := range cidrs {
|
||||
if cidr.Contains(address) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -18,50 +18,115 @@ package proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
pxapi "github.com/Telmate/proxmox-api-go/proxmox"
|
||||
|
||||
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/cluster"
|
||||
goproxmox "github.com/sergelogvinov/go-proxmox"
|
||||
providerconfig "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/config"
|
||||
metrics "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/metrics"
|
||||
provider "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/provider"
|
||||
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/proxmoxpool"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
cloudproviderapi "k8s.io/cloud-provider/api"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type instances struct {
|
||||
c *cluster.Cluster
|
||||
type instanceNetops struct {
|
||||
ExternalCIDRs []*net.IPNet
|
||||
SortOrder []*net.IPNet
|
||||
IgnoredCIDRs []*net.IPNet
|
||||
Mode providerconfig.NetworkMode
|
||||
IPv6SupportDisabled bool
|
||||
}
|
||||
|
||||
func newInstances(client *cluster.Cluster) *instances {
|
||||
type instanceInfo struct {
|
||||
ID int
|
||||
UUID string
|
||||
Name string
|
||||
Type string
|
||||
Node string
|
||||
Region string
|
||||
Zone string
|
||||
}
|
||||
|
||||
type instances struct {
|
||||
c *client
|
||||
zoneAsHAGroup bool
|
||||
provider providerconfig.Provider
|
||||
networkOpts instanceNetops
|
||||
updateLabels bool
|
||||
}
|
||||
|
||||
var instanceTypeNameRegexp = regexp.MustCompile(`(^[a-zA-Z0-9_.-]+)$`)
|
||||
|
||||
func newInstances(client *client, features providerconfig.ClustersFeatures) *instances {
|
||||
externalIPCIDRs := ParseCIDRList(features.Network.ExternalIPCIDRS)
|
||||
if len(features.Network.ExternalIPCIDRS) > 0 && len(externalIPCIDRs) == 0 {
|
||||
klog.Warningf("Failed to parse external CIDRs: %v", features.Network.ExternalIPCIDRS)
|
||||
}
|
||||
|
||||
sortOrderCIDRs, ignoredCIDRs, err := ParseCIDRRuleset(features.Network.IPSortOrder)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to parse sort order CIDRs: %v", err)
|
||||
}
|
||||
|
||||
if len(features.Network.IPSortOrder) > 0 && (len(sortOrderCIDRs)+len(ignoredCIDRs)) == 0 {
|
||||
klog.Warningf("Failed to parse sort order CIDRs: %v", features.Network.IPSortOrder)
|
||||
}
|
||||
|
||||
netOps := instanceNetops{
|
||||
ExternalCIDRs: externalIPCIDRs,
|
||||
SortOrder: sortOrderCIDRs,
|
||||
IgnoredCIDRs: ignoredCIDRs,
|
||||
Mode: features.Network.Mode,
|
||||
IPv6SupportDisabled: features.Network.IPv6SupportDisabled,
|
||||
}
|
||||
|
||||
return &instances{
|
||||
c: client,
|
||||
c: client,
|
||||
zoneAsHAGroup: features.HAGroup,
|
||||
provider: features.Provider,
|
||||
networkOpts: netOps,
|
||||
updateLabels: features.ForceUpdateLabels,
|
||||
}
|
||||
}
|
||||
|
||||
// InstanceExists returns true if the instance for the given node exists according to the cloud provider.
|
||||
// Use the node.name or node.spec.providerID field to find the node in the cloud provider.
|
||||
func (i *instances) InstanceExists(_ context.Context, node *v1.Node) (bool, error) {
|
||||
klog.V(4).Info("instances.InstanceExists() called node: ", node.Name)
|
||||
func (i *instances) InstanceExists(ctx context.Context, node *v1.Node) (bool, error) {
|
||||
klog.V(4).InfoS("instances.InstanceExists() called", "node", klog.KRef("", node.Name))
|
||||
|
||||
if !strings.HasPrefix(node.Spec.ProviderID, ProviderName) {
|
||||
klog.V(4).Infof("instances.InstanceExists() node %s has foreign providerID: %s, skipped", node.Name, node.Spec.ProviderID)
|
||||
if node.Spec.ProviderID == "" {
|
||||
klog.V(4).InfoS("instances.InstanceExists() empty providerID, omitting unmanaged node", "node", klog.KObj(node))
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
_, _, err := i.getInstance(node)
|
||||
if err != nil {
|
||||
if err == cloudprovider.InstanceNotFound {
|
||||
klog.V(4).Infof("instances.InstanceExists() instance %s not found", node.Name)
|
||||
if !strings.HasPrefix(node.Spec.ProviderID, provider.ProviderName) {
|
||||
klog.V(4).InfoS("instances.InstanceExists() omitting unmanaged node", "node", klog.KObj(node), "providerID", node.Spec.ProviderID)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
mc := metrics.NewMetricContext("getVmInfo")
|
||||
if _, err := i.getInstanceInfo(ctx, node); mc.ObserveRequest(err) != nil {
|
||||
if errors.Is(err, cloudprovider.InstanceNotFound) {
|
||||
klog.V(4).InfoS("instances.InstanceExists() instance not found", "node", klog.KObj(node), "providerID", node.Spec.ProviderID)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if errors.Is(err, proxmoxpool.ErrNodeInaccessible) {
|
||||
klog.V(4).InfoS("instances.InstanceExists() proxmox node inaccessible, cannot define instance status", "node", klog.KObj(node), "providerID", node.Spec.ProviderID)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -70,35 +135,50 @@ func (i *instances) InstanceExists(_ context.Context, node *v1.Node) (bool, erro
|
||||
|
||||
// InstanceShutdown returns true if the instance is shutdown according to the cloud provider.
|
||||
// Use the node.name or node.spec.providerID field to find the node in the cloud provider.
|
||||
func (i *instances) InstanceShutdown(_ context.Context, node *v1.Node) (bool, error) {
|
||||
klog.V(4).Info("instances.InstanceShutdown() called, node: ", node.Name)
|
||||
func (i *instances) InstanceShutdown(ctx context.Context, node *v1.Node) (bool, error) {
|
||||
klog.V(4).InfoS("instances.InstanceShutdown() called", "node", klog.KRef("", node.Name))
|
||||
|
||||
if !strings.HasPrefix(node.Spec.ProviderID, ProviderName) {
|
||||
klog.V(4).Infof("instances.InstanceShutdown() node %s has foreign providerID: %s, skipped", node.Name, node.Spec.ProviderID)
|
||||
if node.Spec.ProviderID == "" {
|
||||
klog.V(4).InfoS("instances.InstanceShutdown() empty providerID, omitting unmanaged node", "node", klog.KObj(node))
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
vmr, region, err := i.parseProviderID(node.Spec.ProviderID)
|
||||
if err != nil {
|
||||
klog.Errorf("instances.InstanceShutdown() failed to parse providerID %s: %v", node.Spec.ProviderID, err)
|
||||
if !strings.HasPrefix(node.Spec.ProviderID, provider.ProviderName) {
|
||||
klog.V(4).InfoS("instances.InstanceShutdown() omitting unmanaged node", "node", klog.KObj(node), "providerID", node.Spec.ProviderID)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
px, err := i.c.GetProxmoxCluster(region)
|
||||
vmID, region, err := provider.ParseProviderID(node.Spec.ProviderID)
|
||||
if err != nil {
|
||||
klog.Errorf("instances.InstanceShutdown() failed to get Proxmox cluster: %v", err)
|
||||
if i.provider == providerconfig.ProviderDefault {
|
||||
klog.ErrorS(err, "instances.InstanceShutdown() failed to parse providerID", "providerID", node.Spec.ProviderID)
|
||||
}
|
||||
|
||||
vmID, region, err = i.parseProviderIDFromNode(node)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "instances.InstanceShutdown() failed to parse providerID from node", "node", klog.KObj(node))
|
||||
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
px, err := i.c.pxpool.GetProxmoxCluster(region)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "instances.InstanceShutdown() failed to get Proxmox cluster", "region", region)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
vmState, err := px.GetVmState(vmr)
|
||||
if err != nil {
|
||||
mc := metrics.NewMetricContext("getVmState")
|
||||
|
||||
vm, err := px.GetVMByID(ctx, uint64(vmID))
|
||||
if mc.ObserveRequest(err) != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if vmState["status"].(string) == "stopped" {
|
||||
if vm.Status == "stopped" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -108,124 +188,225 @@ func (i *instances) InstanceShutdown(_ context.Context, node *v1.Node) (bool, er
|
||||
// InstanceMetadata returns the instance's metadata. The values returned in InstanceMetadata are
|
||||
// translated into specific fields in the Node object on registration.
|
||||
// Use the node.name or node.spec.providerID field to find the node in the cloud provider.
|
||||
func (i *instances) InstanceMetadata(_ context.Context, node *v1.Node) (*cloudprovider.InstanceMetadata, error) {
|
||||
klog.V(4).Info("instances.InstanceMetadata() called, node: ", node.Name)
|
||||
func (i *instances) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloudprovider.InstanceMetadata, error) {
|
||||
klog.V(4).InfoS("instances.InstanceMetadata() called", "node", klog.KRef("", node.Name))
|
||||
|
||||
if providedIP, ok := node.ObjectMeta.Annotations[cloudproviderapi.AnnotationAlphaProvidedIPAddr]; ok {
|
||||
var (
|
||||
vmRef *pxapi.VmRef
|
||||
region string
|
||||
err error
|
||||
)
|
||||
var (
|
||||
info *instanceInfo
|
||||
err error
|
||||
)
|
||||
|
||||
providerID := node.Spec.ProviderID
|
||||
if providerID == "" {
|
||||
klog.V(4).Infof("instances.InstanceMetadata() - trying to find providerID for node %s", node.Name)
|
||||
providerID := node.Spec.ProviderID
|
||||
if providerID != "" && !strings.HasPrefix(providerID, provider.ProviderName) {
|
||||
klog.V(4).InfoS("instances.InstanceMetadata() omitting unmanaged node", "node", klog.KObj(node), "providerID", providerID)
|
||||
|
||||
vmRef, region, err = i.c.FindVMByName(node.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("instances.InstanceMetadata() - failed to find instance by name %s: %v, skipped", node.Name, err)
|
||||
}
|
||||
} else if !strings.HasPrefix(node.Spec.ProviderID, ProviderName) {
|
||||
klog.V(4).Infof("instances.InstanceMetadata() node %s has foreign providerID: %s, skipped", node.Name, node.Spec.ProviderID)
|
||||
return &cloudprovider.InstanceMetadata{}, nil
|
||||
}
|
||||
|
||||
mc := metrics.NewMetricContext("getInstanceInfo")
|
||||
|
||||
info, err = i.getInstanceInfo(ctx, node)
|
||||
if mc.ObserveRequest(err) != nil {
|
||||
klog.ErrorS(err, "instances.InstanceMetadata() failed to get instance info", "node", klog.KObj(node))
|
||||
|
||||
if errors.Is(err, cloudprovider.InstanceNotFound) {
|
||||
klog.V(4).InfoS("instances.InstanceMetadata() instance not found", "node", klog.KObj(node), "providerID", providerID)
|
||||
|
||||
return &cloudprovider.InstanceMetadata{}, nil
|
||||
}
|
||||
|
||||
if vmRef == nil {
|
||||
vmRef, region, err = i.getInstance(node)
|
||||
if err != nil {
|
||||
if errors.Is(err, proxmoxpool.ErrNodeInaccessible) {
|
||||
klog.V(4).InfoS("instances.InstanceMetadata() proxmox node inaccessible, cannot get instance metadata", "node", klog.KObj(node), "providerID", providerID)
|
||||
|
||||
return &cloudprovider.InstanceMetadata{}, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
annotations := map[string]string{}
|
||||
labels := map[string]string{
|
||||
LabelTopologyRegion: info.Region,
|
||||
LabelTopologyZone: info.Zone,
|
||||
}
|
||||
|
||||
if providerID == "" {
|
||||
if i.provider == providerconfig.ProviderCapmox {
|
||||
providerID = provider.GetProviderIDFromUUID(info.UUID)
|
||||
annotations[AnnotationProxmoxInstanceID] = fmt.Sprintf("%d", info.ID)
|
||||
} else {
|
||||
providerID = provider.GetProviderIDFromID(info.Region, info.ID)
|
||||
}
|
||||
}
|
||||
|
||||
metadata := &cloudprovider.InstanceMetadata{
|
||||
ProviderID: providerID,
|
||||
NodeAddresses: i.addresses(ctx, node, info),
|
||||
InstanceType: info.Type,
|
||||
Zone: info.Zone,
|
||||
Region: info.Region,
|
||||
AdditionalLabels: labels,
|
||||
}
|
||||
|
||||
haGroups, err := i.c.pxpool.GetNodeHAGroups(ctx, info.Region, info.Node)
|
||||
if err != nil {
|
||||
if !errors.Is(err, proxmoxpool.ErrHAGroupNotFound) {
|
||||
klog.ErrorS(err, "instances.InstanceMetadata() failed to get HA group for the node", "node", klog.KRef("", node.Name), "region", info.Region)
|
||||
}
|
||||
}
|
||||
|
||||
for _, g := range haGroups {
|
||||
labels[LabelTopologyHAGroupPrefix+g] = ""
|
||||
}
|
||||
|
||||
if i.zoneAsHAGroup {
|
||||
if len(haGroups) == 0 {
|
||||
err := fmt.Errorf("cannot set zone as HA-Group")
|
||||
klog.ErrorS(err, "instances.InstanceMetadata() no HA groups found for the node", "node", klog.KRef("", node.Name))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadata.Zone = haGroups[0]
|
||||
labels[LabelTopologyZone] = haGroups[0]
|
||||
}
|
||||
|
||||
if !hasUninitializedTaint(node) {
|
||||
if i.updateLabels {
|
||||
labels[v1.LabelTopologyZone] = metadata.Zone
|
||||
labels[v1.LabelFailureDomainBetaZone] = metadata.Zone
|
||||
labels[v1.LabelTopologyRegion] = metadata.Region
|
||||
labels[v1.LabelFailureDomainBetaRegion] = metadata.Region
|
||||
}
|
||||
|
||||
if len(labels) > 0 {
|
||||
if err := syncNodeLabels(i.c, node, labels); err != nil {
|
||||
klog.ErrorS(err, "error updating labels for the node", "node", klog.KRef("", node.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(annotations) > 0 {
|
||||
if err := syncNodeAnnotations(ctx, i.c.kclient, node, annotations); err != nil {
|
||||
klog.ErrorS(err, "error updating annotations for the node", "node", klog.KRef("", node.Name))
|
||||
}
|
||||
}
|
||||
|
||||
klog.V(5).InfoS("instances.InstanceMetadata()", "info", info, "metadata", metadata)
|
||||
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (i *instances) getInstanceInfo(ctx context.Context, node *v1.Node) (*instanceInfo, error) {
|
||||
klog.V(4).InfoS("instances.getInstanceInfo() called", "node", klog.KRef("", node.Name), "provider", i.provider)
|
||||
|
||||
var (
|
||||
vmID int
|
||||
region string
|
||||
err error
|
||||
)
|
||||
|
||||
providerID := node.Spec.ProviderID
|
||||
|
||||
vmID, region, err = provider.ParseProviderID(providerID)
|
||||
if err != nil {
|
||||
if i.provider == providerconfig.ProviderDefault {
|
||||
klog.ErrorS(err, "instances.getInstanceInfo() failed to parse providerID", "node", klog.KObj(node), "providerID", providerID)
|
||||
}
|
||||
|
||||
vmID, region, err = i.parseProviderIDFromNode(node)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "instances.getInstanceInfo() failed to parse providerID from node", "node", klog.KObj(node))
|
||||
}
|
||||
}
|
||||
|
||||
if vmID == 0 || region == "" {
|
||||
klog.V(4).InfoS("instances.getInstanceInfo() trying to find node in cluster", "node", klog.KObj(node), "providerID", providerID)
|
||||
|
||||
mc := metrics.NewMetricContext("findVmByNode")
|
||||
|
||||
vmID, region, err = i.c.pxpool.FindVMByNode(ctx, node)
|
||||
if mc.ObserveRequest(err) != nil {
|
||||
mc := metrics.NewMetricContext("findVmByUUID")
|
||||
|
||||
vmID, region, err = i.c.pxpool.FindVMByUUID(ctx, node.Status.NodeInfo.SystemUUID)
|
||||
if mc.ObserveRequest(err) != nil {
|
||||
if errors.Is(err, proxmoxpool.ErrInstanceNotFound) {
|
||||
return nil, cloudprovider.InstanceNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
addresses := []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: providedIP}}
|
||||
addresses = append(addresses, v1.NodeAddress{Type: v1.NodeHostName, Address: node.Name})
|
||||
|
||||
instanceType, err := i.getInstanceType(vmRef, region)
|
||||
if err != nil {
|
||||
instanceType = vmRef.GetVmType()
|
||||
}
|
||||
|
||||
return &cloudprovider.InstanceMetadata{
|
||||
ProviderID: i.getProviderID(region, vmRef),
|
||||
NodeAddresses: addresses,
|
||||
InstanceType: instanceType,
|
||||
Zone: vmRef.Node(),
|
||||
Region: region,
|
||||
}, nil
|
||||
}
|
||||
|
||||
klog.Infof("instances.InstanceMetadata() is kubelet has --cloud-provider=external on the node %s?", node.Name)
|
||||
|
||||
return &cloudprovider.InstanceMetadata{}, nil
|
||||
}
|
||||
|
||||
func (i *instances) getInstance(node *v1.Node) (*pxapi.VmRef, string, error) {
|
||||
vm, region, err := i.parseProviderID(node.Spec.ProviderID)
|
||||
px, err := i.c.pxpool.GetProxmoxCluster(region)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("instances.getInstance() error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
px, err := i.c.GetProxmoxCluster(region)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("instances.getInstance() error: %v", err)
|
||||
}
|
||||
mc := metrics.NewMetricContext("getVMConfig")
|
||||
|
||||
vmInfo, err := px.GetVmInfo(vm)
|
||||
if err != nil {
|
||||
vm, err := px.GetVMConfig(ctx, vmID)
|
||||
if mc.ObserveRequest(err) != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
return nil, "", cloudprovider.InstanceNotFound
|
||||
return nil, cloudprovider.InstanceNotFound
|
||||
}
|
||||
|
||||
return nil, "", err
|
||||
if errors.Is(err, goproxmox.ErrVirtualMachineUnreachable) {
|
||||
return nil, proxmoxpool.ErrNodeInaccessible
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vmInfo["name"] != nil && vmInfo["name"].(string) != node.Name {
|
||||
return nil, "", fmt.Errorf("instances.getInstance() vm.name(%s) != node.name(%s)", vmInfo["name"].(string), node.Name)
|
||||
info := &instanceInfo{
|
||||
ID: vmID,
|
||||
UUID: goproxmox.GetVMUUID(vm),
|
||||
Name: vm.Name,
|
||||
Node: vm.Node,
|
||||
Region: region,
|
||||
Zone: vm.Node,
|
||||
}
|
||||
|
||||
klog.V(5).Infof("instances.getInstance() vmInfo %+v", vmInfo)
|
||||
if info.UUID != node.Status.NodeInfo.SystemUUID {
|
||||
klog.Errorf("instances.getInstanceInfo() node %s does not match SystemUUID=%s", info.Name, node.Status.NodeInfo.SystemUUID)
|
||||
|
||||
return vm, region, nil
|
||||
return nil, cloudprovider.InstanceNotFound
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(info.Name, node.Name) {
|
||||
klog.Errorf("instances.getInstanceInfo() node %s does not match VM name=%s", node.Name, info.Name)
|
||||
|
||||
return nil, cloudprovider.InstanceNotFound
|
||||
}
|
||||
|
||||
info.Type = goproxmox.GetVMSKU(vm)
|
||||
if !instanceTypeNameRegexp.MatchString(info.Type) {
|
||||
info.Type = fmt.Sprintf("%dVCPU-%dGB", vm.CPUs, vm.MaxMem/1024/1024/1024)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (i *instances) getInstanceType(vmRef *pxapi.VmRef, region string) (string, error) {
|
||||
px, err := i.c.GetProxmoxCluster(region)
|
||||
if err != nil {
|
||||
return "", err
|
||||
func (i *instances) parseProviderIDFromNode(node *v1.Node) (vmID int, region string, err error) {
|
||||
if node.Annotations[AnnotationProxmoxInstanceID] != "" {
|
||||
region = node.Labels[LabelTopologyRegion]
|
||||
if region == "" {
|
||||
region = node.Labels[v1.LabelTopologyRegion]
|
||||
}
|
||||
|
||||
vmID, err = strconv.Atoi(node.Annotations[AnnotationProxmoxInstanceID])
|
||||
if err != nil {
|
||||
return 0, "", fmt.Errorf("instances.getProviderIDFromNode() parse annotation error: %v", err)
|
||||
}
|
||||
|
||||
if _, err := i.c.pxpool.GetProxmoxCluster(region); err != nil {
|
||||
return 0, "", fmt.Errorf("instances.getProviderIDFromNode() get cluster error: %v", err)
|
||||
}
|
||||
|
||||
return vmID, region, nil
|
||||
}
|
||||
|
||||
vmInfo, err := px.GetVmInfo(vmRef)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.0fVCPU-%.0fGB",
|
||||
vmInfo["maxcpu"].(float64),
|
||||
vmInfo["maxmem"].(float64)/1024/1024/1024), nil
|
||||
}
|
||||
|
||||
var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `://([^/]*)/([^/]+)$`)
|
||||
|
||||
func (i *instances) getProviderID(region string, vmr *pxapi.VmRef) string {
|
||||
return fmt.Sprintf("%s://%s/%d", ProviderName, region, vmr.VmId())
|
||||
}
|
||||
|
||||
func (i *instances) parseProviderID(providerID string) (*pxapi.VmRef, string, error) {
|
||||
if !strings.HasPrefix(providerID, ProviderName) {
|
||||
return nil, "", fmt.Errorf("foreign providerID or empty \"%s\"", providerID)
|
||||
}
|
||||
|
||||
matches := providerIDRegexp.FindStringSubmatch(providerID)
|
||||
if len(matches) != 3 {
|
||||
return nil, "", fmt.Errorf("providerID \"%s\" didn't match expected format \"%s://region/InstanceID\"", providerID, ProviderName)
|
||||
}
|
||||
|
||||
vmID, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("providerID \"%s\" didn't match expected format \"%s://region/InstanceID\"", providerID, ProviderName)
|
||||
}
|
||||
|
||||
return pxapi.NewVmRef(vmID), matches[1], nil
|
||||
return 0, "", fmt.Errorf("instances.getProviderIDFromNode() no annotation found")
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
28
pkg/proxmox/labels.go
Normal file
28
pkg/proxmox/labels.go
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright 2023 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 proxmox
|
||||
|
||||
const (
|
||||
// LabelTopologyRegion is the label used to store the Proxmox region name.
|
||||
LabelTopologyRegion = "topology." + Group + "/region"
|
||||
|
||||
// LabelTopologyZone is the label used to store the Proxmox zone name.
|
||||
LabelTopologyZone = "topology." + Group + "/zone"
|
||||
|
||||
// LabelTopologyHAGroupPrefix is the prefix for labels used to store Proxmox HA group information.
|
||||
LabelTopologyHAGroupPrefix = "group.topology." + Group + "/"
|
||||
)
|
||||
184
pkg/proxmox/utils.go
Normal file
184
pkg/proxmox/utils.go
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
Copyright 2023 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 proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
clientkubernetes "k8s.io/client-go/kubernetes"
|
||||
cloudproviderapi "k8s.io/cloud-provider/api"
|
||||
cloudnodeutil "k8s.io/cloud-provider/node/helpers"
|
||||
)
|
||||
|
||||
// ErrorCIDRConflict is the error message formatting string for CIDR conflicts
|
||||
const ErrorCIDRConflict = "CIDR %s intersects with ignored CIDR %s"
|
||||
|
||||
var uninitializedTaint = &corev1.Taint{
|
||||
Key: cloudproviderapi.TaintExternalCloudProvider,
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
}
|
||||
|
||||
// SplitTrim splits a string of values separated by sep rune into a slice of
|
||||
// strings with trimmed spaces.
|
||||
func SplitTrim(s string, sep rune) []string {
|
||||
f := func(c rune) bool {
|
||||
return unicode.IsSpace(c) || c == sep
|
||||
}
|
||||
|
||||
return strings.FieldsFunc(s, f)
|
||||
}
|
||||
|
||||
// ParseCIDRRuleset parses a comma separated list of CIDRs and returns two slices of *net.IPNet, the first being the allow list, the second be the disallow list
|
||||
func ParseCIDRRuleset(cidrList string) (allowList, ignoreList []*net.IPNet, err error) {
|
||||
cidrlist := SplitTrim(cidrList, ',')
|
||||
if len(cidrlist) == 0 {
|
||||
return []*net.IPNet{}, []*net.IPNet{}, nil
|
||||
}
|
||||
|
||||
for _, item := range cidrlist {
|
||||
item, isIgnore := strings.CutPrefix(item, "!")
|
||||
|
||||
_, cidr, err := net.ParseCIDR(item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if isIgnore {
|
||||
ignoreList = append(ignoreList, cidr)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
allowList = append(allowList, cidr)
|
||||
}
|
||||
|
||||
// Check for no interactions
|
||||
for _, n1 := range allowList {
|
||||
for _, n2 := range ignoreList {
|
||||
if checkIPIntersects(n1, n2) {
|
||||
return nil, nil, fmt.Errorf(ErrorCIDRConflict, n1.String(), n2.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ignoreList, allowList, nil
|
||||
}
|
||||
|
||||
// ParseCIDRList parses a comma separated list of CIDRs and returns a slice of *net.IPNet ignoring errors
|
||||
func ParseCIDRList(cidrList string) []*net.IPNet {
|
||||
cidrlist := SplitTrim(cidrList, ',')
|
||||
if len(cidrlist) == 0 {
|
||||
return []*net.IPNet{}
|
||||
}
|
||||
|
||||
cidrs := make([]*net.IPNet, 0, len(cidrlist))
|
||||
|
||||
for _, item := range cidrlist {
|
||||
_, cidr, err := net.ParseCIDR(item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
cidrs = append(cidrs, cidr)
|
||||
}
|
||||
|
||||
return cidrs
|
||||
}
|
||||
|
||||
func checkIPIntersects(n1, n2 *net.IPNet) bool {
|
||||
return n2.Contains(n1.IP) || n1.Contains(n2.IP)
|
||||
}
|
||||
|
||||
func hasUninitializedTaint(node *corev1.Node) bool {
|
||||
for _, taint := range node.Spec.Taints {
|
||||
if taint.MatchTaint(uninitializedTaint) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func syncNodeAnnotations(ctx context.Context, kclient clientkubernetes.Interface, node *corev1.Node, nodeAnnotations map[string]string) error {
|
||||
nodeAnnotationsOrig := node.ObjectMeta.Annotations
|
||||
annotationsToUpdate := map[string]string{}
|
||||
|
||||
for k, v := range nodeAnnotations {
|
||||
if r, ok := nodeAnnotationsOrig[k]; !ok || r != v {
|
||||
annotationsToUpdate[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(annotationsToUpdate) > 0 {
|
||||
oldData, err := json.Marshal(node)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal the existing node %#v: %w", node, err)
|
||||
}
|
||||
|
||||
newNode := node.DeepCopy()
|
||||
if newNode.Annotations == nil {
|
||||
newNode.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
maps.Copy(newNode.Annotations, annotationsToUpdate)
|
||||
|
||||
newData, err := json.Marshal(newNode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal the new node %#v: %w", newNode, err)
|
||||
}
|
||||
|
||||
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, &corev1.Node{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create a two-way merge patch: %v", err)
|
||||
}
|
||||
|
||||
if _, err := kclient.CoreV1().Nodes().Patch(ctx, node.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}); err != nil {
|
||||
return fmt.Errorf("failed to patch the node: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func syncNodeLabels(c *client, node *corev1.Node, nodeLabels map[string]string) error {
|
||||
nodeLabelsOrig := node.ObjectMeta.Labels
|
||||
labelsToUpdate := map[string]string{}
|
||||
|
||||
for k, v := range nodeLabels {
|
||||
if r, ok := nodeLabelsOrig[k]; !ok || r != v {
|
||||
labelsToUpdate[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(labelsToUpdate) > 0 {
|
||||
if !cloudnodeutil.AddOrUpdateLabelsOnNode(c.kclient, labelsToUpdate, node) {
|
||||
return fmt.Errorf("failed update labels for node %s", node.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
94
pkg/proxmox/utils_test.go
Normal file
94
pkg/proxmox/utils_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2023 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 proxmox_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
proxmox "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/proxmox"
|
||||
)
|
||||
|
||||
func TestParseCIDRRuleset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
cidrs string
|
||||
expectedAllowList []*net.IPNet
|
||||
expectedIgnoreList []*net.IPNet
|
||||
expectedError []any
|
||||
}{
|
||||
{
|
||||
msg: "Empty CIDR ruleset",
|
||||
cidrs: "",
|
||||
expectedAllowList: []*net.IPNet{},
|
||||
expectedIgnoreList: []*net.IPNet{},
|
||||
expectedError: []any{},
|
||||
},
|
||||
{
|
||||
msg: "Conflicting CIDRs",
|
||||
cidrs: "192.168.0.1/16,!192.168.0.1/24",
|
||||
expectedAllowList: []*net.IPNet{},
|
||||
expectedIgnoreList: []*net.IPNet{},
|
||||
expectedError: []any{"192.168.0.0/16", "192.168.0.0/24"},
|
||||
},
|
||||
{
|
||||
msg: "Ignores invalid CIDRs",
|
||||
cidrs: "722.887.0.1/16,!588.0.1/24",
|
||||
expectedAllowList: []*net.IPNet{},
|
||||
expectedIgnoreList: []*net.IPNet{},
|
||||
expectedError: []any{},
|
||||
},
|
||||
{
|
||||
msg: "Valid CIDRs with ignore",
|
||||
cidrs: "192.168.0.1/16,!10.0.0.5/8,144.0.0.7/16,!13.0.0.9/8",
|
||||
expectedAllowList: []*net.IPNet{mustParseCIDR("192.168.0.0/16"), mustParseCIDR("144.0.0.0/16")},
|
||||
expectedIgnoreList: []*net.IPNet{mustParseCIDR("10.0.0.0/8"), mustParseCIDR("13.0.0.0/8")},
|
||||
expectedError: []any{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(fmt.Sprint(testCase.msg), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
allowList, ignoreList, err := proxmox.ParseCIDRRuleset(testCase.cidrs)
|
||||
|
||||
assert.Equal(t, len(testCase.expectedAllowList), len(allowList), "Allow list length mismatch")
|
||||
assert.Equal(t, len(testCase.expectedIgnoreList), len(ignoreList), "Allow list length mismatch")
|
||||
|
||||
if len(testCase.expectedError) != 0 {
|
||||
assert.EqualError(t, err, fmt.Sprintf(proxmox.ErrorCIDRConflict, testCase.expectedError...), "Error mismatch")
|
||||
} else {
|
||||
assert.NoError(t, err, "Unexpected error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustParseCIDR(cidr string) *net.IPNet {
|
||||
_, parsedCIDR, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to parse CIDR %s: %v", cidr, err))
|
||||
}
|
||||
|
||||
return parsedCIDR
|
||||
}
|
||||
17
pkg/proxmoxpool/doc.go
Normal file
17
pkg/proxmoxpool/doc.go
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Copyright 2023 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 proxmoxpool
|
||||
35
pkg/proxmoxpool/errors.go
Normal file
35
pkg/proxmoxpool/errors.go
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2023 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 proxmoxpool
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
var (
|
||||
// ErrClustersNotFound is returned when a cluster is not found in the Proxmox
|
||||
ErrClustersNotFound = errors.New("clusters not found")
|
||||
// ErrHAGroupNotFound is returned when a ha-group is not found in the Proxmox
|
||||
ErrHAGroupNotFound = errors.New("ha-group not found")
|
||||
// ErrRegionNotFound is returned when a region is not found in the Proxmox
|
||||
ErrRegionNotFound = errors.New("region not found")
|
||||
// ErrZoneNotFound is returned when a zone is not found in the Proxmox
|
||||
ErrZoneNotFound = errors.New("zone not found")
|
||||
// ErrInstanceNotFound is returned when an instance is not found in the Proxmox
|
||||
ErrInstanceNotFound = errors.New("instance not found")
|
||||
|
||||
// ErrNodeInaccessible is returned when a Proxmox node cannot be reached or accessed
|
||||
ErrNodeInaccessible = errors.New("node is inaccessible")
|
||||
)
|
||||
336
pkg/proxmoxpool/pool.go
Normal file
336
pkg/proxmoxpool/pool.go
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
Copyright 2023 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 proxmoxpool provides a pool of Telmate/proxmox-api-go/proxmox clients
|
||||
package proxmoxpool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
proxmox "github.com/luthermonson/go-proxmox"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
goproxmox "github.com/sergelogvinov/go-proxmox"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// ProxmoxCluster defines a Proxmox cluster configuration.
|
||||
type ProxmoxCluster struct {
|
||||
URL string `yaml:"url"`
|
||||
Insecure bool `yaml:"insecure,omitempty"`
|
||||
TokenID string `yaml:"token_id,omitempty"`
|
||||
TokenIDFile string `yaml:"token_id_file,omitempty"`
|
||||
TokenSecret string `yaml:"token_secret,omitempty"`
|
||||
TokenSecretFile string `yaml:"token_secret_file,omitempty"`
|
||||
Username string `yaml:"username,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
Region string `yaml:"region,omitempty"`
|
||||
}
|
||||
|
||||
// ProxmoxPool is a Proxmox client pool of proxmox clusters.
|
||||
type ProxmoxPool struct {
|
||||
clients map[string]*goproxmox.APIClient
|
||||
}
|
||||
|
||||
// NewProxmoxPool creates a new Proxmox cluster client.
|
||||
func NewProxmoxPool(config []*ProxmoxCluster, options ...proxmox.Option) (*ProxmoxPool, error) {
|
||||
clusters := len(config)
|
||||
if clusters > 0 {
|
||||
clients := make(map[string]*goproxmox.APIClient, clusters)
|
||||
|
||||
for _, cfg := range config {
|
||||
opts := []proxmox.Option{proxmox.WithUserAgent("ProxmoxCCM/1.0")}
|
||||
opts = append(opts, options...)
|
||||
|
||||
if cfg.Insecure {
|
||||
httpTr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
}
|
||||
|
||||
opts = append(opts, proxmox.WithHTTPClient(&http.Client{Transport: httpTr}))
|
||||
}
|
||||
|
||||
if cfg.TokenID == "" && cfg.TokenIDFile != "" {
|
||||
var err error
|
||||
|
||||
cfg.TokenID, err = readValueFromFile(cfg.TokenIDFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.TokenSecret == "" && cfg.TokenSecretFile != "" {
|
||||
var err error
|
||||
|
||||
cfg.TokenSecret, err = readValueFromFile(cfg.TokenSecretFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Username != "" && cfg.Password != "" {
|
||||
opts = append(opts, proxmox.WithCredentials(&proxmox.Credentials{
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
}))
|
||||
} else if cfg.TokenID != "" && cfg.TokenSecret != "" {
|
||||
opts = append(opts, proxmox.WithAPIToken(cfg.TokenID, cfg.TokenSecret))
|
||||
}
|
||||
|
||||
pxClient, err := goproxmox.NewAPIClient(cfg.URL, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clients[cfg.Region] = pxClient
|
||||
}
|
||||
|
||||
return &ProxmoxPool{
|
||||
clients: clients,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, ErrClustersNotFound
|
||||
}
|
||||
|
||||
// GetRegions returns supported regions.
|
||||
func (c *ProxmoxPool) GetRegions() []string {
|
||||
regions := make([]string, 0, len(c.clients))
|
||||
|
||||
for region := range c.clients {
|
||||
regions = append(regions, region)
|
||||
}
|
||||
|
||||
return regions
|
||||
}
|
||||
|
||||
// CheckClusters checks if the Proxmox connection is working.
|
||||
func (c *ProxmoxPool) CheckClusters(ctx context.Context) error {
|
||||
for region, pxClient := range c.clients {
|
||||
info, err := pxClient.Version(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialized proxmox client in region %s, error: %v", region, err)
|
||||
}
|
||||
|
||||
cluster := (&proxmox.Cluster{}).New(pxClient.Client)
|
||||
|
||||
// Check if we can have permission to list VMs
|
||||
vms, err := cluster.Resources(ctx, "vm")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get list of VMs in region %s, error: %v", region, err)
|
||||
}
|
||||
|
||||
if len(vms) > 0 {
|
||||
klog.V(4).InfoS("Proxmox cluster information", "region", region, "version", info.Version, "vms", len(vms))
|
||||
} else {
|
||||
klog.InfoS("Proxmox cluster has no VMs, or check the account permission", "region", region)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProxmoxCluster returns a Proxmox cluster client in a given region.
|
||||
func (c *ProxmoxPool) GetProxmoxCluster(region string) (*goproxmox.APIClient, error) {
|
||||
if c.clients[region] != nil {
|
||||
return c.clients[region], nil
|
||||
}
|
||||
|
||||
return nil, ErrRegionNotFound
|
||||
}
|
||||
|
||||
// GetVMByIDInRegion returns a Proxmox VM by its ID in a given region.
|
||||
func (c *ProxmoxPool) GetVMByIDInRegion(ctx context.Context, region string, vmid uint64) (*proxmox.ClusterResource, error) {
|
||||
px, err := c.GetProxmoxCluster(region)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vm, err := px.GetVMByID(ctx, uint64(vmid)) //nolint: unconvert
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vm, nil
|
||||
}
|
||||
|
||||
// DeleteVMByIDInRegion deletes a Proxmox VM by its ID in a given region.
|
||||
func (c *ProxmoxPool) DeleteVMByIDInRegion(ctx context.Context, region string, vm *proxmox.ClusterResource) error {
|
||||
px, err := c.GetProxmoxCluster(region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return px.DeleteVMByID(ctx, vm.Node, int(vm.VMID))
|
||||
}
|
||||
|
||||
// GetNodeHAGroups returns a Proxmox node ha-group in a given region for the node.
|
||||
func (c *ProxmoxPool) GetNodeHAGroups(ctx context.Context, region string, node string) ([]string, error) {
|
||||
groups := []string{}
|
||||
|
||||
px, err := c.GetProxmoxCluster(region)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
haGroups, err := px.GetHAGroupList(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error get ha-groups %v", err)
|
||||
}
|
||||
|
||||
for _, g := range haGroups {
|
||||
if g.Type != "group" {
|
||||
continue
|
||||
}
|
||||
|
||||
for n := range strings.SplitSeq(g.Nodes, ",") {
|
||||
if node == strings.Split(n, ":")[0] {
|
||||
groups = append(groups, g.Group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(groups) > 0 {
|
||||
slices.Sort(groups)
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
return nil, ErrHAGroupNotFound
|
||||
}
|
||||
|
||||
// FindVMByNode find a VM by kubernetes node resource in all Proxmox clusters.
|
||||
func (c *ProxmoxPool) FindVMByNode(ctx context.Context, node *v1.Node) (vmID int, region string, err error) {
|
||||
var errs error
|
||||
|
||||
for region, px := range c.clients {
|
||||
vm, err := px.GetVMByFilter(ctx, func(rs *proxmox.ClusterResource) (bool, error) {
|
||||
if rs.Type != "qemu" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(rs.Name, node.Name) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if rs.Status == "unknown" {
|
||||
errs = multierr.Append(errs, fmt.Errorf("region %s node %s: %w", region, rs.Node, ErrNodeInaccessible))
|
||||
|
||||
return false, nil //nolint: nilerr
|
||||
}
|
||||
|
||||
vm, err := px.GetVMConfig(ctx, int(rs.VMID))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if goproxmox.GetVMUUID(vm) == node.Status.NodeInfo.SystemUUID {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
if err == goproxmox.ErrVirtualMachineNotFound {
|
||||
continue
|
||||
}
|
||||
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
if vm.VMID == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return int(vm.VMID), region, nil
|
||||
}
|
||||
|
||||
if errs != nil {
|
||||
return 0, "", errs
|
||||
}
|
||||
|
||||
return 0, "", ErrInstanceNotFound
|
||||
}
|
||||
|
||||
// FindVMByUUID find a VM by uuid in all Proxmox clusters.
|
||||
func (c *ProxmoxPool) FindVMByUUID(ctx context.Context, uuid string) (vmID int, region string, err error) {
|
||||
var errs error
|
||||
|
||||
for region, px := range c.clients {
|
||||
vm, err := px.GetVMByFilter(ctx, func(rs *proxmox.ClusterResource) (bool, error) {
|
||||
if rs.Type != "qemu" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if rs.Status == "unknown" {
|
||||
errs = multierr.Append(errs, fmt.Errorf("region %s node %s: %w", region, rs.Node, ErrNodeInaccessible))
|
||||
|
||||
return false, nil //nolint: nilerr
|
||||
}
|
||||
|
||||
vm, err := px.GetVMConfig(ctx, int(rs.VMID))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if goproxmox.GetVMUUID(vm) == uuid {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, goproxmox.ErrVirtualMachineNotFound) {
|
||||
continue
|
||||
}
|
||||
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
return int(vm.VMID), region, nil
|
||||
}
|
||||
|
||||
if errs != nil {
|
||||
return 0, "", errs
|
||||
}
|
||||
|
||||
return 0, "", ErrInstanceNotFound
|
||||
}
|
||||
|
||||
func readValueFromFile(path string) (string, error) {
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("path cannot be empty")
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read file '%s': %w", path, err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(content)), nil
|
||||
}
|
||||
119
pkg/proxmoxpool/pool_test.go
Normal file
119
pkg/proxmoxpool/pool_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2023 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 proxmoxpool_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
pxpool "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/proxmoxpool"
|
||||
)
|
||||
|
||||
func newClusterEnv() []*pxpool.ProxmoxCluster {
|
||||
cfg := []*pxpool.ProxmoxCluster{
|
||||
{
|
||||
URL: "https://127.0.0.1:8006/api2/json",
|
||||
Insecure: false,
|
||||
TokenID: "user!token-id",
|
||||
TokenSecret: "secret",
|
||||
Region: "cluster-1",
|
||||
},
|
||||
{
|
||||
URL: "https://127.0.0.2:8006/api2/json",
|
||||
Insecure: false,
|
||||
TokenID: "user!token-id",
|
||||
TokenSecret: "secret",
|
||||
Region: "cluster-2",
|
||||
},
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func newClusterEnvWithFiles(tokenIDPath, tokenSecretPath string) []*pxpool.ProxmoxCluster {
|
||||
cfg := []*pxpool.ProxmoxCluster{
|
||||
{
|
||||
URL: "https://127.0.0.1:8006/api2/json",
|
||||
Insecure: false,
|
||||
TokenIDFile: tokenIDPath,
|
||||
TokenSecretFile: tokenSecretPath,
|
||||
Region: "cluster-1",
|
||||
},
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
cfg := newClusterEnv()
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
pxClient, err := pxpool.NewProxmoxPool([]*pxpool.ProxmoxCluster{})
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, pxClient)
|
||||
|
||||
pxClient, err = pxpool.NewProxmoxPool(cfg)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, pxClient)
|
||||
}
|
||||
|
||||
func TestNewClientWithCredentialsFromFile(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
tokenIDFile, err := os.CreateTemp(tempDir, "token_id")
|
||||
assert.Nil(t, err)
|
||||
|
||||
tokenSecretFile, err := os.CreateTemp(tempDir, "token_secret")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = tokenIDFile.WriteString("user!token-id")
|
||||
assert.Nil(t, err)
|
||||
_, err = tokenSecretFile.WriteString("secret")
|
||||
assert.Nil(t, err)
|
||||
|
||||
cfg := newClusterEnvWithFiles(tokenIDFile.Name(), tokenSecretFile.Name())
|
||||
|
||||
pxClient, err := pxpool.NewProxmoxPool(cfg)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, pxClient)
|
||||
assert.Equal(t, "user!token-id", cfg[0].TokenID)
|
||||
assert.Equal(t, "secret", cfg[0].TokenSecret)
|
||||
}
|
||||
|
||||
func TestCheckClusters(t *testing.T) {
|
||||
cfg := newClusterEnv()
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
pxClient, err := pxpool.NewProxmoxPool(cfg)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, pxClient)
|
||||
|
||||
pxapi, err := pxClient.GetProxmoxCluster("test")
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, pxapi)
|
||||
assert.Equal(t, pxpool.ErrRegionNotFound, err)
|
||||
|
||||
pxapi, err = pxClient.GetProxmoxCluster("cluster-1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, pxapi)
|
||||
|
||||
err = pxClient.CheckClusters(t.Context())
|
||||
assert.NotNil(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to initialized proxmox client in region")
|
||||
}
|
||||
482
test/cluster/cluster.go
Normal file
482
test/cluster/cluster.go
Normal file
@@ -0,0 +1,482 @@
|
||||
/*
|
||||
Copyright 2023 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 cluster
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/luthermonson/go-proxmox"
|
||||
|
||||
goproxmox "github.com/sergelogvinov/go-proxmox"
|
||||
)
|
||||
|
||||
// SetupMockResponders sets up the HTTP mock responders for Proxmox API calls.
|
||||
func SetupMockResponders() {
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/version$`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.Version{Version: "8.4"},
|
||||
})
|
||||
})
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/cluster/status`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.NodeStatuses{{Name: "pve-1"}, {Name: "pve-2"}, {Name: "pve-3"}, {Name: "pve-4"}},
|
||||
})
|
||||
})
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/cluster/ha/groups`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": []goproxmox.HAGroup{
|
||||
{Group: "rnd", Type: "group", Nodes: "pve-1,pve-2"},
|
||||
{Group: "dev", Type: "group", Nodes: "pve-4"},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, "https://127.0.0.2:8006/api2/json/cluster/resources",
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.ClusterResources{
|
||||
&proxmox.ClusterResource{
|
||||
Node: "pve-3",
|
||||
Type: "qemu",
|
||||
VMID: 103,
|
||||
Name: "cluster-2-node-1",
|
||||
MaxCPU: 2,
|
||||
MaxMem: 5 * 1024 * 1024 * 1024,
|
||||
Status: "stopped",
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
httpmock.RegisterResponder(http.MethodGet, "=~/cluster/resources",
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.ClusterResources{
|
||||
&proxmox.ClusterResource{
|
||||
Node: "pve-1",
|
||||
Type: "qemu",
|
||||
VMID: 100,
|
||||
Name: "cluster-1-node-1",
|
||||
MaxCPU: 4,
|
||||
MaxMem: 10 * 1024 * 1024 * 1024,
|
||||
Status: "running",
|
||||
},
|
||||
&proxmox.ClusterResource{
|
||||
Node: "pve-2",
|
||||
Type: "qemu",
|
||||
VMID: 101,
|
||||
Name: "cluster-1-node-2",
|
||||
MaxCPU: 2,
|
||||
MaxMem: 5 * 1024 * 1024 * 1024,
|
||||
Status: "running",
|
||||
},
|
||||
&proxmox.ClusterResource{
|
||||
Node: "pve-4",
|
||||
Type: "qemu",
|
||||
VMID: 104,
|
||||
Name: "cluster-1-node-4",
|
||||
MaxCPU: 2,
|
||||
MaxMem: 4 * 1024 * 1024 * 1024,
|
||||
Status: "unknown",
|
||||
},
|
||||
|
||||
&proxmox.ClusterResource{
|
||||
ID: "storage/smb",
|
||||
Type: "storage",
|
||||
PluginType: "cifs",
|
||||
Node: "pve-1",
|
||||
Storage: "smb",
|
||||
Content: "rootdir,images",
|
||||
Shared: 1,
|
||||
Status: "available",
|
||||
},
|
||||
&proxmox.ClusterResource{
|
||||
ID: "storage/rbd",
|
||||
Type: "storage",
|
||||
PluginType: "dir",
|
||||
Node: "pve-1",
|
||||
Storage: "rbd",
|
||||
Content: "images",
|
||||
Shared: 1,
|
||||
Status: "available",
|
||||
},
|
||||
&proxmox.ClusterResource{
|
||||
ID: "storage/zfs",
|
||||
Type: "storage",
|
||||
PluginType: "zfspool",
|
||||
Node: "pve-1",
|
||||
Storage: "zfs",
|
||||
Content: "images",
|
||||
Status: "available",
|
||||
},
|
||||
&proxmox.ClusterResource{
|
||||
ID: "storage/zfs",
|
||||
Type: "storage",
|
||||
PluginType: "zfspool",
|
||||
Node: "pve-2",
|
||||
Storage: "zfs",
|
||||
Content: "images",
|
||||
Status: "available",
|
||||
},
|
||||
&proxmox.ClusterResource{
|
||||
ID: "storage/zfs",
|
||||
Type: "storage",
|
||||
PluginType: "zfspool",
|
||||
Node: "pve-4",
|
||||
Storage: "zfs",
|
||||
Content: "images",
|
||||
Status: "unknown",
|
||||
},
|
||||
&proxmox.ClusterResource{
|
||||
ID: "storage/lvm",
|
||||
Type: "storage",
|
||||
PluginType: "lvm",
|
||||
Node: "pve-1",
|
||||
Storage: "local-lvm",
|
||||
Content: "images",
|
||||
Status: "available",
|
||||
},
|
||||
&proxmox.ClusterResource{
|
||||
ID: "storage/lvm",
|
||||
Type: "storage",
|
||||
PluginType: "lvm",
|
||||
Node: "pve-2",
|
||||
Storage: "local-lvm",
|
||||
Content: "images",
|
||||
Status: "available",
|
||||
},
|
||||
&proxmox.ClusterResource{
|
||||
ID: "storage/lvm",
|
||||
Type: "storage",
|
||||
PluginType: "lvm",
|
||||
Node: "pve-4",
|
||||
Storage: "local-lvm",
|
||||
Content: "images",
|
||||
Status: "unknown",
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-1/status`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.Node{},
|
||||
})
|
||||
})
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-2/status`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.Node{},
|
||||
})
|
||||
})
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-3/status`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.Node{},
|
||||
})
|
||||
})
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-4/status`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewBytesResponse(595, []byte{}), nil
|
||||
})
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, "=~/nodes$",
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": []proxmox.NodeStatus{
|
||||
{
|
||||
Node: "pve-1",
|
||||
Status: "online",
|
||||
},
|
||||
{
|
||||
Node: "pve-2",
|
||||
Status: "online",
|
||||
},
|
||||
{
|
||||
Node: "pve-3",
|
||||
Status: "online",
|
||||
},
|
||||
{
|
||||
Node: "pve-4",
|
||||
Status: "offline",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/\S+/storage/rbd/status`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.Storage{
|
||||
Type: "dir",
|
||||
Enabled: 1,
|
||||
Active: 1,
|
||||
Shared: 1,
|
||||
Content: "images",
|
||||
Total: 100 * 1024 * 1024 * 1024,
|
||||
Used: 50 * 1024 * 1024 * 1024,
|
||||
Avail: 50 * 1024 * 1024 * 1024,
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/\S+/storage/zfs/status`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.Storage{
|
||||
Type: "zfspool",
|
||||
Enabled: 1,
|
||||
Active: 1,
|
||||
Content: "images",
|
||||
Total: 100 * 1024 * 1024 * 1024,
|
||||
Used: 50 * 1024 * 1024 * 1024,
|
||||
Avail: 50 * 1024 * 1024 * 1024,
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/\S+/storage/local-lvm/status`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.Storage{
|
||||
Type: "lvmthin",
|
||||
Enabled: 1,
|
||||
Active: 1,
|
||||
Content: "images",
|
||||
Total: 100 * 1024 * 1024 * 1024,
|
||||
Used: 50 * 1024 * 1024 * 1024,
|
||||
Avail: 50 * 1024 * 1024 * 1024,
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/\S+/storage/\S+/status`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(400, map[string]any{
|
||||
"data": nil,
|
||||
"message": "Parameter verification failed",
|
||||
"errors": map[string]string{
|
||||
"storage": "No such storage.",
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/\S+/storage/smb/content`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": []proxmox.StorageContent{
|
||||
{
|
||||
Format: "raw",
|
||||
Volid: "smb:9999/vm-9999-volume-smb.raw",
|
||||
VMID: 9999,
|
||||
Size: 1024 * 1024 * 1024,
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/\S+/storage/rbd/content`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": []proxmox.StorageContent{
|
||||
{
|
||||
Format: "raw",
|
||||
Volid: "rbd:9999/vm-9999-volume-rbd.raw",
|
||||
VMID: 9999,
|
||||
Size: 1024 * 1024 * 1024,
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-1/qemu$`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": []proxmox.VirtualMachine{
|
||||
{
|
||||
VMID: 100,
|
||||
Status: "running",
|
||||
Name: "cluster-1-node-1",
|
||||
Node: "pve-1",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-2/qemu$`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": []proxmox.VirtualMachine{
|
||||
{
|
||||
VMID: 101,
|
||||
Status: "running",
|
||||
Name: "cluster-1-node-2",
|
||||
Node: "pve-2",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-3/qemu$`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": []proxmox.VirtualMachine{
|
||||
{
|
||||
VMID: 103,
|
||||
Status: "stopped",
|
||||
Name: "cluster-2-node-1",
|
||||
Node: "pve-3",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-4/qemu$`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewBytesResponse(595, []byte{}), nil
|
||||
})
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-1/qemu/100/status/current`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.VirtualMachine{
|
||||
VMID: 100,
|
||||
Name: "cluster-1-node-1",
|
||||
Node: "pve-1",
|
||||
CPUs: 4,
|
||||
MaxMem: 10 * 1024 * 1024 * 1024,
|
||||
Status: "running",
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-1/qemu/100/config`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": map[string]any{
|
||||
"vmid": 100,
|
||||
"cores": 4,
|
||||
"memory": "10240",
|
||||
"scsi0": "local-lvm:vm-100-disk-0,size=10G",
|
||||
"scsi1": "local-lvm:vm-9999-pvc-123,backup=0,iothread=1,wwn=0x5056432d49443031",
|
||||
"smbios1": "uuid=11833f4c-341f-4bd3-aad7-f7abed000000",
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-2/qemu/101/status/current`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.VirtualMachine{
|
||||
VMID: 101,
|
||||
Name: "cluster-1-node-2",
|
||||
Node: "pve-2",
|
||||
CPUs: 2,
|
||||
MaxMem: 5 * 1024 * 1024 * 1024,
|
||||
Status: "running",
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-2/qemu/101/config`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": map[string]any{
|
||||
"vmid": 101,
|
||||
"scsi0": "local-lvm:vm-101-disk-0,size=10G",
|
||||
"scsi1": "local-lvm:vm-101-disk-1,size=1G",
|
||||
"scsi3": "local-lvm:vm-101-disk-2,size=1G",
|
||||
"smbios1": "uuid=11833f4c-341f-4bd3-aad7-f7abed000001",
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-3/qemu/103/status/current`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": proxmox.VirtualMachine{
|
||||
VMID: 103,
|
||||
Name: "cluster-2-node-1",
|
||||
Node: "pve-3",
|
||||
CPUs: 1,
|
||||
MaxMem: 2 * 1024 * 1024 * 1024,
|
||||
Status: "running",
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-3/qemu/103/config`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": map[string]any{
|
||||
"vmid": 103,
|
||||
"smbios1": "uuid=11833f4c-341f-4bd3-aad7-f7abea000000,sku=YzEubWVkaXVt",
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
httpmock.RegisterResponder(http.MethodGet, `=~/nodes/pve-4/qemu/`,
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewBytesResponse(595, []byte{}), nil
|
||||
},
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder(http.MethodPut, "https://127.0.0.1:8006/api2/json/nodes/pve-1/qemu/100/resize",
|
||||
func(_ *http.Request) (*http.Response, error) {
|
||||
return httpmock.NewJsonResponse(200, map[string]any{
|
||||
"data": "",
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
task := &proxmox.Task{
|
||||
UPID: "UPID:pve-1:003B4235:1DF4ABCA:667C1C45:csi:103:root@pam:",
|
||||
Type: "delete",
|
||||
User: "root",
|
||||
Status: "completed",
|
||||
Node: "pve-1",
|
||||
IsRunning: false,
|
||||
}
|
||||
|
||||
taskErr := &proxmox.Task{
|
||||
UPID: "UPID:pve-1:003B4235:1DF4ABCA:667C1C45:csi:104:root@pam:",
|
||||
Type: "delete",
|
||||
User: "root",
|
||||
Status: "stopped",
|
||||
ExitStatus: "ERROR",
|
||||
Node: "pve-1",
|
||||
IsRunning: false,
|
||||
}
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, fmt.Sprintf(`=~/nodes/%s/tasks/%s/status`, "pve-1", string(task.UPID)),
|
||||
httpmock.NewJsonResponderOrPanic(200, map[string]any{"data": task}))
|
||||
httpmock.RegisterResponder(http.MethodGet, fmt.Sprintf(`=~/nodes/%s/tasks/%s/status`, "pve-1", string(taskErr.UPID)),
|
||||
httpmock.NewJsonResponderOrPanic(200, map[string]any{"data": taskErr}))
|
||||
|
||||
httpmock.RegisterResponder(http.MethodDelete, `=~/nodes/pve-1/storage/local-lvm/content/vm-9999-pvc-123`,
|
||||
httpmock.NewJsonResponderOrPanic(200, map[string]any{"data": task.UPID}).Times(1))
|
||||
httpmock.RegisterResponder(http.MethodDelete, `=~/nodes/pve-1/storage/local-lvm/content/vm-9999-pvc-error`,
|
||||
httpmock.NewJsonResponderOrPanic(200, map[string]any{"data": taskErr.UPID}).Times(1))
|
||||
}
|
||||
18
test/cluster/docs.go
Normal file
18
test/cluster/docs.go
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2023 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 cluster implements the http mock server for testing purposes.
|
||||
package cluster
|
||||
13
test/config/cluster-config-1.yaml
Normal file
13
test/config/cluster-config-1.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
features:
|
||||
provider: default
|
||||
clusters:
|
||||
- url: https://127.0.0.1:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "user!token-id"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
- url: https://127.0.0.2:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "user!token-id"
|
||||
token_secret: "secret"
|
||||
region: cluster-2
|
||||
13
test/config/cluster-config-2.yaml
Normal file
13
test/config/cluster-config-2.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
features:
|
||||
provider: capmox
|
||||
clusters:
|
||||
- url: https://127.0.0.1:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "user!token-id"
|
||||
token_secret: "secret"
|
||||
region: cluster-1
|
||||
- url: https://127.0.0.2:8006/api2/json
|
||||
insecure: false
|
||||
token_id: "user!token-id"
|
||||
token_secret: "secret"
|
||||
region: cluster-2
|
||||
Reference in New Issue
Block a user