mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-03-26 19:45:06 +00:00
Compare commits
282 Commits
v1.10.1
...
v1.11.1-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdbe7eb242 | ||
|
|
5afe837f76 | ||
|
|
350cb6dec6 | ||
|
|
ef23da3289 | ||
|
|
6862fb84b9 | ||
|
|
c8e405c89b | ||
|
|
5836a2a0c9 | ||
|
|
a1e08f4eec | ||
|
|
61a08ccc30 | ||
|
|
46a355c293 | ||
|
|
1cb966da57 | ||
|
|
286db706e9 | ||
|
|
f33ea376e9 | ||
|
|
1349e570f9 | ||
|
|
ba8465b87d | ||
|
|
7dccc17690 | ||
|
|
9b922782e1 | ||
|
|
dc0a712089 | ||
|
|
d6755f7953 | ||
|
|
6ac085316d | ||
|
|
0da2baa908 | ||
|
|
8628388445 | ||
|
|
495063b4f6 | ||
|
|
87794d4615 | ||
|
|
c3e7fd7a74 | ||
|
|
5c0c378797 | ||
|
|
7d0d56e5fa | ||
|
|
3c9570fd14 | ||
|
|
971396110f | ||
|
|
9de61aa5a0 | ||
|
|
5f3cb25311 | ||
|
|
e16cb76892 | ||
|
|
bacec117b9 | ||
|
|
af0d2addfc | ||
|
|
35bf7a085d | ||
|
|
0c1a57af72 | ||
|
|
079c76ffb5 | ||
|
|
18c6cd0400 | ||
|
|
9f460a91e7 | ||
|
|
f9a3d7e2f2 | ||
|
|
1e31bcf406 | ||
|
|
892e52456d | ||
|
|
56f93393d8 | ||
|
|
3a09e8aa23 | ||
|
|
0fb64fa581 | ||
|
|
d932b3dcbb | ||
|
|
73f1740407 | ||
|
|
86df02f7f6 | ||
|
|
f1ddf0a6a2 | ||
|
|
f0ca2ae7ad | ||
|
|
42ec72146d | ||
|
|
fc692c49e6 | ||
|
|
d429d38ea1 | ||
|
|
491664e10d | ||
|
|
2e121ac360 | ||
|
|
c0366bb8fb | ||
|
|
95c674b23a | ||
|
|
c1acd9c6c5 | ||
|
|
cccbd2f8c0 | ||
|
|
b428b09a78 | ||
|
|
05c4e35ae7 | ||
|
|
f031121214 | ||
|
|
83f176e4ac | ||
|
|
c9af70aff3 | ||
|
|
112775f924 | ||
|
|
7e9896807d | ||
|
|
478fb27a0e | ||
|
|
5b8ec80ad8 | ||
|
|
096330df16 | ||
|
|
caaf87c478 | ||
|
|
2dc8a920ca | ||
|
|
15d44724e7 | ||
|
|
82358666c8 | ||
|
|
a3cef5b0d3 | ||
|
|
4de4d37833 | ||
|
|
a0137e2eca | ||
|
|
838af53d3a | ||
|
|
58ad42871b | ||
|
|
433d2d5e57 | ||
|
|
29b5894be6 | ||
|
|
2ad43194aa | ||
|
|
086dbd344f | ||
|
|
ec88dc5203 | ||
|
|
36f9ae6983 | ||
|
|
ac87154348 | ||
|
|
115f32cae5 | ||
|
|
458560795b | ||
|
|
e4f2f52392 | ||
|
|
2c26c1d5fe | ||
|
|
5c4b5509c2 | ||
|
|
e500e2d8e5 | ||
|
|
446e43d018 | ||
|
|
81bee240fe | ||
|
|
29f3557bb4 | ||
|
|
10ae2b3e3a | ||
|
|
2155b2b215 | ||
|
|
5505110c4a | ||
|
|
2c21cec7e4 | ||
|
|
c677c433e0 | ||
|
|
117d5e846f | ||
|
|
ad9c6e8dee | ||
|
|
c58854fc41 | ||
|
|
a0dac73c95 | ||
|
|
1d8ca4f2ef | ||
|
|
f527a1fc62 | ||
|
|
d6a3da2929 | ||
|
|
a1e4f54488 | ||
|
|
eeee4e06d2 | ||
|
|
a2621caa74 | ||
|
|
dd63e8182c | ||
|
|
36163c9a0e | ||
|
|
ec4a7072b3 | ||
|
|
54042c3b01 | ||
|
|
085493a830 | ||
|
|
7d7e3fff0d | ||
|
|
6d8f086283 | ||
|
|
40aae5ebdd | ||
|
|
c6059a93d2 | ||
|
|
7d8cb990e0 | ||
|
|
6a295cb0bb | ||
|
|
38a7707ce3 | ||
|
|
2dab3446d8 | ||
|
|
28d636bd71 | ||
|
|
44a065bd3f | ||
|
|
8bed159023 | ||
|
|
e80584f1a9 | ||
|
|
018ea42bd0 | ||
|
|
6d635a9454 | ||
|
|
0acc698ddf | ||
|
|
9d42c1a408 | ||
|
|
c6c6908b1a | ||
|
|
3c671a7c09 | ||
|
|
d23307b403 | ||
|
|
d72e88a74b | ||
|
|
99c622331a | ||
|
|
c3d1d83da5 | ||
|
|
94fec66bc8 | ||
|
|
7b3b2c28d2 | ||
|
|
357a917c4e | ||
|
|
e671615e58 | ||
|
|
da17641433 | ||
|
|
eb4ecd3767 | ||
|
|
c6fba5556e | ||
|
|
c2ac76165e | ||
|
|
8c7363d6a7 | ||
|
|
a467488f1a | ||
|
|
6163df5da2 | ||
|
|
b23c541010 | ||
|
|
4b1488bbc3 | ||
|
|
707001e9d4 | ||
|
|
0a2aed8967 | ||
|
|
979fb9ccab | ||
|
|
1730f8bcb4 | ||
|
|
d7defa7fb5 | ||
|
|
08b8498afb | ||
|
|
42a92e9b3d | ||
|
|
5555f7d4e7 | ||
|
|
16bf3e2d90 | ||
|
|
fb1dc110f2 | ||
|
|
5f039b7f7c | ||
|
|
6be07a1df3 | ||
|
|
a83c153ca1 | ||
|
|
4d0c3ac83f | ||
|
|
beed887eeb | ||
|
|
fa58a775e8 | ||
|
|
8c3ddf0f73 | ||
|
|
c5efb542d0 | ||
|
|
44bcc0959e | ||
|
|
8f76907aff | ||
|
|
ef05af13bf | ||
|
|
0be05c9bc8 | ||
|
|
731a484275 | ||
|
|
7139daf07a | ||
|
|
6257060bb6 | ||
|
|
3be7c33d3b | ||
|
|
53c3f4b436 | ||
|
|
0933dd906f | ||
|
|
0fd5af3300 | ||
|
|
5db9437f5e | ||
|
|
19b855660a | ||
|
|
53f3d13d7c | ||
|
|
218fd76411 | ||
|
|
a761111ba1 | ||
|
|
843c70959f | ||
|
|
d7738532c8 | ||
|
|
0b6b841f2a | ||
|
|
745d573dfa | ||
|
|
9d1ccedd44 | ||
|
|
8b0afa3c44 | ||
|
|
2b043f7bdf | ||
|
|
a0891c6f44 | ||
|
|
51568525cb | ||
|
|
428415c004 | ||
|
|
a5a165b0c3 | ||
|
|
358e3b8554 | ||
|
|
fb5ee2e7bf | ||
|
|
955eec7033 | ||
|
|
d14879ff74 | ||
|
|
b0a16ceac1 | ||
|
|
71b459dff9 | ||
|
|
e6c8f3afa5 | ||
|
|
cf2b482c97 | ||
|
|
dd847c2846 | ||
|
|
a1027eeb52 | ||
|
|
d23418b5b5 | ||
|
|
601f4a9985 | ||
|
|
5899287399 | ||
|
|
eb284fd5d1 | ||
|
|
2574229fb0 | ||
|
|
2c4cfe5611 | ||
|
|
598333dca1 | ||
|
|
d1608e7723 | ||
|
|
46bcdb2c50 | ||
|
|
01c4e9b0c9 | ||
|
|
7d916485ec | ||
|
|
fc98268181 | ||
|
|
e8ea414af7 | ||
|
|
9f6f13f0c5 | ||
|
|
ab642ffff2 | ||
|
|
700d9dcc36 | ||
|
|
9a54142257 | ||
|
|
70b4238013 | ||
|
|
10a1428e00 | ||
|
|
c27c395d50 | ||
|
|
b10503b351 | ||
|
|
95fcd8f63c | ||
|
|
722aead2fd | ||
|
|
78682d7cc3 | ||
|
|
62c00ba841 | ||
|
|
54427705c7 | ||
|
|
5b03da2637 | ||
|
|
2abb176bd8 | ||
|
|
544df59f58 | ||
|
|
32eb8655cc | ||
|
|
bd370b2215 | ||
|
|
3b903e678f | ||
|
|
69da593f37 | ||
|
|
88a1317f48 | ||
|
|
55873c1c37 | ||
|
|
ffc9845fb9 | ||
|
|
30b7ed8bf1 | ||
|
|
09098f879c | ||
|
|
2ce46bd50c | ||
|
|
d7f771d0f7 | ||
|
|
3a9ff2256b | ||
|
|
00fe0dcaf0 | ||
|
|
24faca31da | ||
|
|
2f3732fa44 | ||
|
|
b51a17138e | ||
|
|
807ba7e902 | ||
|
|
9c62a9be81 | ||
|
|
897a5e0bd8 | ||
|
|
2a0ed689c8 | ||
|
|
a462bef9c3 | ||
|
|
d26aaeb41e | ||
|
|
2dce4a7cb5 | ||
|
|
1f41eb49b1 | ||
|
|
342dc4adf9 | ||
|
|
3c3f041bc1 | ||
|
|
fda394744a | ||
|
|
9dbd9694d8 | ||
|
|
270225e89b | ||
|
|
33517aedc5 | ||
|
|
82c6ca7304 | ||
|
|
8a10b9a9e4 | ||
|
|
73a5ee41fa | ||
|
|
069c9a0751 | ||
|
|
6eccaa4cf5 | ||
|
|
8194e8d723 | ||
|
|
11ea0d7561 | ||
|
|
31e2137154 | ||
|
|
a7efd657f4 | ||
|
|
9d3600623e | ||
|
|
59bb4df0db | ||
|
|
a8f04de955 | ||
|
|
16ecc7c7b1 | ||
|
|
7936dc2a9a | ||
|
|
9ae29f747e | ||
|
|
67d6116835 | ||
|
|
a80cfcdb8c | ||
|
|
23c69f46ab | ||
|
|
0bee6dd9fd |
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -5,7 +5,7 @@ about: Tell us about a problem you are experiencing
|
||||
---
|
||||
|
||||
**What steps did you take and what happened:**
|
||||
[A clear and concise description of what the bug is, and what commands you ran.)
|
||||
<!--A clear and concise description of what the bug is, and what commands you ran.-->
|
||||
|
||||
|
||||
**What did you expect to happen:**
|
||||
@@ -25,7 +25,7 @@ Please provide the output of the following commands (Pasting long output into a
|
||||
|
||||
|
||||
**Anything else you would like to add:**
|
||||
[Miscellaneous information that will assist in solving the issue.]
|
||||
<!--Miscellaneous information that will assist in solving the issue.-->
|
||||
|
||||
|
||||
**Environment:**
|
||||
|
||||
@@ -5,15 +5,15 @@ about: Suggest an idea for this project
|
||||
---
|
||||
|
||||
**Describe the problem/challenge you have**
|
||||
[A description of the current limitation/problem/challenge that you are experiencing.]
|
||||
<!--A description of the current limitation/problem/challenge that you are experiencing.-->
|
||||
|
||||
|
||||
**Describe the solution you'd like**
|
||||
[A clear and concise description of what you want to happen.]
|
||||
<!--A clear and concise description of what you want to happen.-->
|
||||
|
||||
|
||||
**Anything else you would like to add:**
|
||||
[Miscellaneous information that will assist in solving the issue.]
|
||||
<!--Miscellaneous information that will assist in solving the issue.-->
|
||||
|
||||
|
||||
**Environment:**
|
||||
|
||||
6
.github/auto-assignees.yml
vendored
6
.github/auto-assignees.yml
vendored
@@ -9,7 +9,6 @@ reviewers:
|
||||
|
||||
groups:
|
||||
maintainers:
|
||||
- dsu-igeek
|
||||
- sseago
|
||||
- reasonerjt
|
||||
- ywk253100
|
||||
@@ -19,7 +18,10 @@ reviewers:
|
||||
- Lyndon-Li
|
||||
|
||||
tech-writer:
|
||||
- a-mccarthy
|
||||
- sseago
|
||||
- reasonerjt
|
||||
- ywk253100
|
||||
- Lyndon-Li
|
||||
|
||||
files:
|
||||
'site/**':
|
||||
|
||||
2
.github/workflows/crds-verify-kind.yaml
vendored
2
.github/workflows/crds-verify-kind.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.10
|
||||
go-version: '1.20.6'
|
||||
id: go
|
||||
# Look for a CLI that's made for this PR
|
||||
- name: Fetch built CLI
|
||||
|
||||
4
.github/workflows/e2e-test-kind.yaml
vendored
4
.github/workflows/e2e-test-kind.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.10
|
||||
go-version: '1.20.6'
|
||||
id: go
|
||||
# Look for a CLI that's made for this PR
|
||||
- name: Fetch built CLI
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.10
|
||||
go-version: '1.20.6'
|
||||
id: go
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
36
.github/workflows/nightly-trivy-scan.yml
vendored
Normal file
36
.github/workflows/nightly-trivy-scan.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Trivy Nightly Scan
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC
|
||||
|
||||
jobs:
|
||||
nightly-scan:
|
||||
name: Trivy nightly scan
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# maintain the versions of Velero those need security scan
|
||||
versions: [main]
|
||||
# list of images that need scan
|
||||
images: [velero, velero-restore-helper]
|
||||
permissions:
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image-ref: 'docker.io/velero/${{ matrix.images }}:${{ matrix.versions }}'
|
||||
severity: 'CRITICAL,HIGH,MEDIUM'
|
||||
format: 'template'
|
||||
template: '@/contrib/sarif.tpl'
|
||||
output: 'trivy-results.sarif'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
9
.github/workflows/pr-ci-check.yml
vendored
9
.github/workflows/pr-ci-check.yml
vendored
@@ -4,11 +4,13 @@ jobs:
|
||||
build:
|
||||
name: Run CI
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.10
|
||||
go-version: '1.20.6'
|
||||
id: go
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v2
|
||||
@@ -27,3 +29,8 @@ jobs:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: coverage.out
|
||||
verbose: true
|
||||
- name: Run staticcheck
|
||||
uses: dominikh/staticcheck-action@v1.3.0
|
||||
with:
|
||||
version: "2023.1.3"
|
||||
install-go: false
|
||||
2
.github/workflows/pr-codespell.yml
vendored
2
.github/workflows/pr-codespell.yml
vendored
@@ -15,6 +15,6 @@ jobs:
|
||||
with:
|
||||
# ignore the config/.../crd.go file as it's generated binary data that is edited elswhere.
|
||||
skip: .git,*.png,*.jpg,*.woff,*.ttf,*.gif,*.ico,./config/crd/v1beta1/crds/crds.go,./config/crd/v1/crds/crds.go,./go.sum,./LICENSE
|
||||
ignore_words_list: iam,aks,ist,bridget,ue,shouldnot
|
||||
ignore_words_list: iam,aks,ist,bridget,ue,shouldnot,atleast
|
||||
check_filenames: true
|
||||
check_hidden: true
|
||||
|
||||
4
.github/workflows/push-builder.yml
vendored
4
.github/workflows/push-builder.yml
vendored
@@ -2,9 +2,7 @@ name: build-image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'release-**'
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'hack/build-image/Dockerfile'
|
||||
|
||||
|
||||
26
.github/workflows/push.yml
vendored
26
.github/workflows/push.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.10
|
||||
go-version: '1.20.6'
|
||||
id: go
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
@@ -60,10 +60,21 @@ jobs:
|
||||
files: coverage.out
|
||||
verbose: true
|
||||
|
||||
# Use the JSON key in secret to login gcr.io
|
||||
- uses: 'docker/login-action@v2'
|
||||
with:
|
||||
registry: 'gcr.io' # or REGION.docker.pkg.dev
|
||||
username: '_json_key'
|
||||
password: '${{ secrets.GCR_SA_KEY }}'
|
||||
|
||||
# Only try to publish the container image from the root repo; forks don't have permission to do so and will always get failures.
|
||||
- name: Publish container image
|
||||
if: github.repository == 'vmware-tanzu/velero'
|
||||
run: |
|
||||
sudo swapoff -a
|
||||
sudo rm -f /mnt/swapfile
|
||||
docker image prune -a --force
|
||||
|
||||
# Build and push Velero image to docker registry
|
||||
docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
|
||||
VERSION=$(./hack/docker-push.sh | grep 'VERSION:' | awk -F: '{print $2}' | xargs)
|
||||
@@ -87,16 +98,3 @@ jobs:
|
||||
uploader ${VELERO_RESTORE_HELPER_IMAGE_FILE} ${GCS_BUCKET}
|
||||
uploader ${VELERO_IMAGE_BACKUP_FILE} ${GCS_BUCKET}
|
||||
uploader ${VELERO_RESTORE_HELPER_IMAGE_BACKUP_FILE} ${GCS_BUCKET}
|
||||
|
||||
# Use the JSON key in secret to login gcr.io
|
||||
- uses: 'docker/login-action@v1'
|
||||
with:
|
||||
registry: 'gcr.io' # or REGION.docker.pkg.dev
|
||||
username: '_json_key'
|
||||
password: '${{ secrets.GCR_SA_KEY }}'
|
||||
|
||||
# Push image to GCR to facilitate some environments that have rate limitation to docker hub, e.g. vSphere.
|
||||
- name: Publish container image to GCR
|
||||
if: github.repository == 'vmware-tanzu/velero'
|
||||
run: |
|
||||
REGISTRY=gcr.io/velero-gcp ./hack/docker-push.sh
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -49,4 +49,6 @@ tilt-resources/deployment.yaml
|
||||
tilt-resources/node-agent.yaml
|
||||
tilt-resources/cloud
|
||||
|
||||
# test generated files
|
||||
test/e2e/report.xml
|
||||
coverage.out
|
||||
|
||||
@@ -54,3 +54,10 @@ release:
|
||||
name: velero
|
||||
draft: true
|
||||
prerelease: auto
|
||||
|
||||
git:
|
||||
# What should be used to sort tags when gathering the current and previous
|
||||
# tags if there are more than one tag in the same commit.
|
||||
#
|
||||
# Default: `-version:refname`
|
||||
tag_sort: -version:creatordate
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
# Velero binary build section
|
||||
FROM --platform=$BUILDPLATFORM golang:1.18.10 as velero-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.20.6-bullseye as velero-builder
|
||||
|
||||
ARG GOPROXY
|
||||
ARG BIN
|
||||
@@ -44,7 +44,7 @@ RUN mkdir -p /output/usr/bin && \
|
||||
-ldflags "${LDFLAGS}" ${PKG}/cmd/${BIN}
|
||||
|
||||
# Restic binary build section
|
||||
FROM --platform=$BUILDPLATFORM golang:1.19.4-bullseye as restic-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.20.6-bullseye as restic-builder
|
||||
|
||||
ARG BIN
|
||||
ARG TARGETOS
|
||||
@@ -66,7 +66,7 @@ RUN mkdir -p /output/usr/bin && \
|
||||
/go/src/github.com/vmware-tanzu/velero/hack/build-restic.sh
|
||||
|
||||
# Velero image packing section
|
||||
FROM gcr.io/distroless/base-debian11@sha256:99133cb0878bb1f84d1753957c6fd4b84f006f2798535de22ebf7ba170bbf434
|
||||
FROM gcr.io/distroless/base-nossl-debian11@sha256:9523ef8cf054e23a81e722d231c6f604ab43a03c5b174b5c8386c78c0b6473d0
|
||||
|
||||
LABEL maintainer="Nolan Brubaker <brubakern@vmware.com>"
|
||||
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
|
||||
## Maintainers
|
||||
|
||||
| Maintainer | GitHub ID | Affiliation |
|
||||
| --------------- | --------- | ----------- |
|
||||
| Dave Smith-Uchida | [dsu-igeek](https://github.com/dsu-igeek) | [Kasten](https://github.com/kastenhq/) |
|
||||
| Scott Seago | [sseago](https://github.com/sseago) | [OpenShift](https://github.com/openshift)
|
||||
| Daniel Jiang | [reasonerjt](https://github.com/reasonerjt) | [VMware](https://www.github.com/vmware/)
|
||||
| Wenkai Yin | [ywk253100](https://github.com/ywk253100) | [VMware](https://www.github.com/vmware/) |
|
||||
| Xun Jiang | [blackpiglet](https://github.com/blackpiglet) | [VMware](https://www.github.com/vmware/) |
|
||||
| Ming Qiu | [qiuming-best](https://github.com/qiuming-best) | [VMware](https://www.github.com/vmware/) |
|
||||
| Shubham Pampattiwar | [shubham-pampattiwar](https://github.com/shubham-pampattiwar) | [OpenShift](https://github.com/openshift)
|
||||
| Yonghui Li | [Lyndon-Li](https://github.com/Lyndon-Li) | [VMware](https://www.github.com/vmware/) |
|
||||
| Maintainer | GitHub ID | Affiliation |
|
||||
|---------------------|---------------------------------------------------------------|-------------------------------------------|
|
||||
| Dave Smith-Uchida | [dsu-igeek](https://github.com/dsu-igeek) | [Kasten](https://github.com/kastenhq/) |
|
||||
| Scott Seago | [sseago](https://github.com/sseago) | [OpenShift](https://github.com/openshift) |
|
||||
| Daniel Jiang | [reasonerjt](https://github.com/reasonerjt) | [VMware](https://www.github.com/vmware/) |
|
||||
| Wenkai Yin | [ywk253100](https://github.com/ywk253100) | [VMware](https://www.github.com/vmware/) |
|
||||
| Xun Jiang | [blackpiglet](https://github.com/blackpiglet) | [VMware](https://www.github.com/vmware/) |
|
||||
| Ming Qiu | [qiuming-best](https://github.com/qiuming-best) | [VMware](https://www.github.com/vmware/) |
|
||||
| Shubham Pampattiwar | [shubham-pampattiwar](https://github.com/shubham-pampattiwar) | [OpenShift](https://github.com/openshift) |
|
||||
| Yonghui Li | [Lyndon-Li](https://github.com/Lyndon-Li) | [VMware](https://www.github.com/vmware/) |
|
||||
|
||||
## Emeritus Maintainers
|
||||
* Adnan Abdulhussein ([prydonius](https://github.com/prydonius))
|
||||
@@ -28,12 +28,12 @@
|
||||
|
||||
## Velero Contributors & Stakeholders
|
||||
|
||||
| Feature Area | Lead |
|
||||
| ----------------------------- | :---------------------: |
|
||||
| Architect | Dave Smith-Uchida [dsu-igeek](https://github.com/dsu-igeek) |
|
||||
| Technical Lead | Daniel Jiang [reasonerjt](https://github.com/reasonerjt) |
|
||||
| Kubernetes CSI Liaison | |
|
||||
| Deployment | |
|
||||
| Community Management | Orlin Vasilev [OrlinVasilev](https://github.com/OrlinVasilev) |
|
||||
| Product Management | Pradeep Kumar Chaturvedi [pradeepkchaturvedi](https://github.com/pradeepkchaturvedi) |
|
||||
| Feature Area | Lead |
|
||||
|------------------------|:------------------------------------------------------------------------------------:|
|
||||
| Architect | Dave Smith-Uchida [dsu-igeek](https://github.com/dsu-igeek) |
|
||||
| Technical Lead | Daniel Jiang [reasonerjt](https://github.com/reasonerjt) |
|
||||
| Kubernetes CSI Liaison | |
|
||||
| Deployment | |
|
||||
| Community Management | Orlin Vasilev [OrlinVasilev](https://github.com/OrlinVasilev) |
|
||||
| Product Management | Pradeep Kumar Chaturvedi [pradeepkchaturvedi](https://github.com/pradeepkchaturvedi) |
|
||||
|
||||
|
||||
24
Makefile
24
Makefile
@@ -22,9 +22,11 @@ PKG := github.com/vmware-tanzu/velero
|
||||
|
||||
# Where to push the docker image.
|
||||
REGISTRY ?= velero
|
||||
GCR_REGISTRY ?= gcr.io/velero-gcp
|
||||
|
||||
# Image name
|
||||
IMAGE ?= $(REGISTRY)/$(BIN)
|
||||
GCR_IMAGE ?= $(GCR_REGISTRY)/$(BIN)
|
||||
|
||||
# We allow the Dockerfile to be configurable to enable the use of custom Dockerfiles
|
||||
# that pull base images from different registries.
|
||||
@@ -66,8 +68,10 @@ TAG_LATEST ?= false
|
||||
|
||||
ifeq ($(TAG_LATEST), true)
|
||||
IMAGE_TAGS ?= $(IMAGE):$(VERSION) $(IMAGE):latest
|
||||
GCR_IMAGE_TAGS ?= $(GCR_IMAGE):$(VERSION) $(GCR_IMAGE):latest
|
||||
else
|
||||
IMAGE_TAGS ?= $(IMAGE):$(VERSION)
|
||||
GCR_IMAGE_TAGS ?= $(GCR_IMAGE):$(VERSION)
|
||||
endif
|
||||
|
||||
ifeq ($(shell docker buildx inspect 2>/dev/null | awk '/Status/ { print $$2 }'), running)
|
||||
@@ -82,7 +86,7 @@ see: https://velero.io/docs/main/build-from-source/#making-images-and-updating-v
|
||||
endef
|
||||
|
||||
# The version of restic binary to be downloaded
|
||||
RESTIC_VERSION ?= 0.14.0
|
||||
RESTIC_VERSION ?= 0.15.0
|
||||
|
||||
CLI_PLATFORMS ?= linux-amd64 linux-arm linux-arm64 darwin-amd64 darwin-arm64 windows-amd64 linux-ppc64le
|
||||
BUILDX_PLATFORMS ?= $(subst -,/,$(ARCH))
|
||||
@@ -96,9 +100,6 @@ else
|
||||
GIT_TREE_STATE ?= clean
|
||||
endif
|
||||
|
||||
# The default linters used by lint and local-lint
|
||||
LINTERS ?= "gosec,goconst,gofmt,goimports,unparam"
|
||||
|
||||
###
|
||||
### These variables should not need tweaking.
|
||||
###
|
||||
@@ -186,6 +187,7 @@ endif
|
||||
--output=type=$(BUILDX_OUTPUT_TYPE) \
|
||||
--platform $(BUILDX_PLATFORMS) \
|
||||
$(addprefix -t , $(IMAGE_TAGS)) \
|
||||
$(addprefix -t , $(GCR_IMAGE_TAGS)) \
|
||||
--build-arg=GOPROXY=$(GOPROXY) \
|
||||
--build-arg=PKG=$(PKG) \
|
||||
--build-arg=BIN=$(BIN) \
|
||||
@@ -221,22 +223,12 @@ endif
|
||||
|
||||
lint:
|
||||
ifneq ($(SKIP_TESTS), 1)
|
||||
@$(MAKE) shell CMD="-c 'hack/lint.sh $(LINTERS)'"
|
||||
@$(MAKE) shell CMD="-c 'hack/lint.sh'"
|
||||
endif
|
||||
|
||||
local-lint:
|
||||
ifneq ($(SKIP_TESTS), 1)
|
||||
@hack/lint.sh $(LINTERS)
|
||||
endif
|
||||
|
||||
lint-all:
|
||||
ifneq ($(SKIP_TESTS), 1)
|
||||
@$(MAKE) shell CMD="-c 'hack/lint.sh $(LINTERS) true'"
|
||||
endif
|
||||
|
||||
local-lint-all:
|
||||
ifneq ($(SKIP_TESTS), 1)
|
||||
@hack/lint.sh $(LINTERS) true
|
||||
@hack/lint.sh
|
||||
endif
|
||||
|
||||
update:
|
||||
|
||||
19
README.md
19
README.md
@@ -1,7 +1,7 @@
|
||||
![100]
|
||||
|
||||
[![Build Status][1]][2] [](https://bestpractices.coreinfrastructure.org/projects/3811)
|
||||
|
||||

|
||||
|
||||
## Overview
|
||||
|
||||
@@ -38,15 +38,12 @@ See [the list of releases][6] to find out about feature changes.
|
||||
|
||||
The following is a list of the supported Kubernetes versions for each Velero version.
|
||||
|
||||
| Velero version | Expected Kubernetes version compatibility| Tested on Kubernetes version|
|
||||
|----------------|--------------------|--------------------|
|
||||
| 1.10 | 1.16-latest | 1.22.5, 1.23.8, 1.24.6 and 1.25.1 |
|
||||
| 1.9 | 1.16-latest | 1.20.5, 1.21.2, 1.22.5, 1.23, and 1.24 |
|
||||
| 1.8 | 1.16-latest | |
|
||||
| 1.6.3-1.7.1 | 1.12-latest ||
|
||||
| 1.60-1.6.2 | 1.12-1.21 ||
|
||||
| 1.5 | 1.12-1.21 ||
|
||||
| 1.4 | 1.10-1.21 | |
|
||||
| Velero version | Expected Kubernetes version compatibility | Tested on Kubernetes version |
|
||||
|----------------|-------------------------------------------|----------------------------------------|
|
||||
| 1.11 | 1.18-latest | 1.23.10, 1.24.9, 1.25.5, and 1.26.1 |
|
||||
| 1.10 | 1.18-latest | 1.22.5, 1.23.8, 1.24.6 and 1.25.1 |
|
||||
| 1.9 | 1.18-latest | 1.20.5, 1.21.2, 1.22.5, 1.23, and 1.24 |
|
||||
| 1.8 | 1.18-latest | |
|
||||
|
||||
Velero supports IPv4, IPv6, and dual stack environments. Support for this was tested against Velero v1.8.
|
||||
|
||||
@@ -54,6 +51,8 @@ The Velero maintainers are continuously working to expand testing coverage, but
|
||||
|
||||
If you are interested in using a different version of Kubernetes with a given Velero version, we'd recommend that you perform testing before installing or upgrading your environment. For full information around capabilities within a release, also see the Velero [release notes](https://github.com/vmware-tanzu/velero/releases) or Kubernetes [release notes](https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG). See the Velero [support page](https://velero.io/docs/latest/support-process/) for information about supported versions of Velero.
|
||||
|
||||
For each release, Velero maintainers run the test to ensure the upgrade path from n-2 minor release. For example, before the release of v1.10.x, the test will verify that the backup created by v1.9.x and v1.8.x can be restored using the build to be tagged as v1.10.x.
|
||||
|
||||
[1]: https://github.com/vmware-tanzu/velero/workflows/Main%20CI/badge.svg
|
||||
[2]: https://github.com/vmware-tanzu/velero/actions?query=workflow%3A"Main+CI"
|
||||
[4]: https://github.com/vmware-tanzu/velero/issues
|
||||
|
||||
4
Tiltfile
4
Tiltfile
@@ -50,7 +50,7 @@ git_sha = str(local("git rev-parse HEAD", quiet = True, echo_off = True)).strip(
|
||||
|
||||
tilt_helper_dockerfile_header = """
|
||||
# Tilt image
|
||||
FROM golang:1.18.10 as tilt-helper
|
||||
FROM golang:1.20.6 as tilt-helper
|
||||
|
||||
# Support live reloading with Tilt
|
||||
RUN wget --output-document /restart.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/restart.sh && \
|
||||
@@ -103,7 +103,7 @@ local_resource(
|
||||
|
||||
local_resource(
|
||||
"restic_binary",
|
||||
cmd = 'cd ' + '.' + ';mkdir -p _tiltbuild/restic; BIN=velero GOOS=linux GOARCH=amd64 RESTIC_VERSION=0.13.1 OUTPUT_DIR=_tiltbuild/restic ./hack/download-restic.sh',
|
||||
cmd = 'cd ' + '.' + ';mkdir -p _tiltbuild/restic; BIN=velero GOOS=linux GOARCH=amd64 GOARM="" RESTIC_VERSION=0.13.1 OUTPUT_DIR=_tiltbuild/restic ./hack/build-restic.sh',
|
||||
)
|
||||
|
||||
# Note: we need a distro with a bash shell to exec into the Velero container
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
* Skip completed jobs and pods when restoring (#463, @nrb)
|
||||
* Set namespace correctly when syncing backups from object storage (#472, @skriss)
|
||||
* When building on macOS, bind-mount volumes with delegated config (#478, @skriss)
|
||||
* Add replica sets and daemonsets to cohabitating resources so they're not backed up twice (#482 #485, @skriss)
|
||||
* Add replica sets and daemonsets to cohabiting resources so they're not backed up twice (#482 #485, @skriss)
|
||||
* Shut down the Ark server gracefully on SIGINT/SIGTERM (#483, @skriss)
|
||||
* Only back up resources that support GET and DELETE in addition to LIST and CREATE (#486, @nrb)
|
||||
* Show a better error message when trying to get an incomplete restore's logs (#496, @nrb)
|
||||
|
||||
@@ -1,30 +1,4 @@
|
||||
## v1.10.1
|
||||
### 2023-01-19
|
||||
|
||||
### Download
|
||||
https://github.com/vmware-tanzu/velero/releases/tag/v1.10.1
|
||||
|
||||
### Container Image
|
||||
`velero/velero:v1.10.1`
|
||||
|
||||
### Documentation
|
||||
https://velero.io/docs/v1.10/
|
||||
|
||||
### Upgrading
|
||||
https://velero.io/docs/v1.10/upgrade-to-1.10/
|
||||
|
||||
### All changes
|
||||
* Fix Restic v0.14.0 HIGH grade CVEs. (#5817, @blackpiglet)
|
||||
* Bump up golang net to fix CVE-2022-41721 (#5811, @Lyndon-Li)
|
||||
* Bump up golang to 1.18.10 for Velero (#5780, @Lyndon-Li)
|
||||
* Add PR container build action, which will not push image. Add GOARM parameter. Remove container-builder-env section. (#5770, @blackpiglet)
|
||||
* Add Restic builder in Dockerfile, and keep the used built Golang image version in accordance with upstream Restic. (#5765, @blackpiglet)
|
||||
* Fix issue 5696, check if the repo is still openable before running the prune and forget operation, if not, try to reconnect the repo (#5714, @Lyndon-Li)
|
||||
* Fix error with Restic backup empty volumes (#5711, @qiuming-best)
|
||||
* Prevent nil panic on exec restore hooks (#5708, @dymurray)
|
||||
* Fix CVEs scanned by trivy (#5655, @qiuming-best)
|
||||
|
||||
## v1.10.0
|
||||
## v1.10.0
|
||||
### 2022-11-23
|
||||
|
||||
### Download
|
||||
|
||||
156
changelogs/CHANGELOG-1.11.md
Normal file
156
changelogs/CHANGELOG-1.11.md
Normal file
@@ -0,0 +1,156 @@
|
||||
## v1.11.1
|
||||
### 2023-07-19
|
||||
|
||||
### Download
|
||||
https://github.com/vmware-tanzu/velero/releases/tag/v1.11.1
|
||||
|
||||
### Container Image
|
||||
`velero/velero:v1.11.1`
|
||||
|
||||
### Documentation
|
||||
https://velero.io/docs/v1.11/
|
||||
|
||||
### Upgrading
|
||||
https://velero.io/docs/v1.11/upgrade-to-1.11/
|
||||
|
||||
### All changes
|
||||
* Add support for OpenStack CSI drivers topology keys (#6488, @kayrus)
|
||||
* Enhance the code because of #6297, the return value of GetBucketRegion is not recorded, as a result, when it fails, we have no way to get the cause (#6477, @Lyndon-Li)
|
||||
* Fixed a bug where status.progress is not getting updated for backups. (#6324, @blackpiglet)
|
||||
* Restore Endpoints before Services (#6316, @ywk253100)
|
||||
* Fix issue #6182. If pod is not running, don't treat it as an error, let it go and leave a warning. (#6189, @Lyndon-Li)
|
||||
|
||||
|
||||
## v1.11
|
||||
### 2023-04-07
|
||||
|
||||
### Download
|
||||
https://github.com/vmware-tanzu/velero/releases/tag/v1.11.0
|
||||
|
||||
### Container Image
|
||||
`velero/velero:v1.11.0`
|
||||
|
||||
### Documentation
|
||||
https://velero.io/docs/v1.11/
|
||||
|
||||
### Upgrading
|
||||
https://velero.io/docs/v1.11/upgrade-to-1.11/
|
||||
|
||||
### Highlights
|
||||
|
||||
#### BackupItemAction v2
|
||||
This feature implements the BackupItemAction v2. BIA v2 has two new methods: Progress() and Cancel() and modifies the Execute() return value.
|
||||
|
||||
The API change is needed to facilitate long-running BackupItemAction plugin actions that may not be complete when the Execute() method returns. This will allow long-running BackupItemAction plugin actions to continue in the background while the Velero moves to the following plugin or the next item.
|
||||
|
||||
#### RestoreItemAction v2
|
||||
This feature implemented the RestoreItemAction v2. RIA v2 has three new methods: Progress(), Cancel(), and AreAdditionalItemsReady(), and it modifies RestoreItemActionExecuteOutput() structure in the RIA return value.
|
||||
|
||||
The Progress() and Cancel() methods are needed to facilitate long-running RestoreItemAction plugin actions that may not be complete when the Execute() method returns. This will allow long-running RestoreItemAction plugin actions to continue in the background while the Velero moves to the following plugin or the next item. The AreAdditionalItemsReady() method is needed to allow plugins to tell Velero to wait until the returned additional items have been restored and are ready for use in the cluster before restoring the current item.
|
||||
|
||||
#### Plugin Progress Monitoring
|
||||
This is intended as a replacement for the previously-approved Upload Progress Monitoring design ([Upload Progress Monitoring](https://github.com/vmware-tanzu/velero/blob/main/design/upload-progress.md)) to expand the supported use cases beyond snapshot upload to include what was previously called Async Backup/Restore Item Actions.
|
||||
|
||||
#### Flexible resource policy that can filter volumes to skip in the backup
|
||||
This feature provides a flexible policy to filter volumes in the backup without requiring patching any labels or annotations to the pods or volumes. This policy is configured as k8s ConfigMap and maintained by the users themselves, and it can be extended to more scenarios in the future. By now, the policy rules out volumes from backup depending on the CSI driver, NFS setting, volume size, and StorageClass setting. Please refer to [Resource policies rules](https://velero.io/docs/v1.11/resource-filtering/#resource-policies) for the policy's ConifgMap format. It is not guaranteed to work on unofficial third-party plugins as it may not follow the existing backup workflow code logic of Velero.
|
||||
|
||||
#### Resource Filters that can distinguish cluster scope and namespace scope resources
|
||||
This feature adds four new resource filters for backup. The new filters are separated into cluster scope and namespace scope. Before this feature, Velero could not filter cluster scope resources precisely. This feature provides the ability and refactors existing resource filter parameters.
|
||||
|
||||
#### New parameter in installation to customize the serviceaccount name
|
||||
The `velero install` sub-command now includes a new parameter,`--service-account-name`, which allows users to specify the ServiceAccountName for the Velero and node-agent pods. This feature may be particularly useful for users who utilize IRSA (IAM Roles for Service Accounts) in Amazon EKS (Elastic Kubernetes Service)."
|
||||
|
||||
#### Add a parameter for setting the Velero server connection with the k8s API server's timeout
|
||||
In Velero, some code pieces need to communicate with the k8s API server. Before v1.11, these code pieces used hard-code timeout settings. This feature adds a resource-timeout parameter in the velero server binary to make it configurable.
|
||||
|
||||
#### Add resource list in the output of the restore describe command
|
||||
Before this feature, Velero restore didn't have a restored resources list as the Velero backup. It's not convenient for users to learn what is restored. This feature adds the resources list and the handling result of the resources (including created, updated, failed, and skipped).
|
||||
|
||||
#### Support JSON format output of backup describe command
|
||||
Before the Velero v1.11 release, users could not choose Velero's backup describe command's output format. The command output format is friendly for human reading, but it's not a structured output, and it's not easy for other programs to get information from it. Velero v1.11 adds a JSON format output for the backup describe command.
|
||||
|
||||
#### Refactor controllers with controller-runtime
|
||||
In v1.11, Backup Controller and Restore controller are refactored with controller-runtime. Till v1.11, all Velero controllers use the controller-runtime framework.
|
||||
|
||||
#### Runtime and dependencies
|
||||
To fix CVEs and keep pace with Golang, Velero made changes as follows:
|
||||
* Bump Golang runtime to v1.19.8.
|
||||
* Bump several dependent libraries to new versions.
|
||||
* Compile Restic (v0.15.0) with Golang v1.19.8 instead of packaging the official binary.
|
||||
|
||||
|
||||
### Breaking changes
|
||||
* The Velero CSI plugin now determines whether to restore Volume's data from snapshots on the restore's restorePVs setting. Before v1.11, the CSI plugin doesn't check the restorePVs parameter setting.
|
||||
|
||||
|
||||
### Limitations/Known issues
|
||||
* The Flexible resource policy that can filter volumes to skip in the backup is not guaranteed to work on unofficial third-party plugins because the plugins may not follow the existing backup workflow code logic of Velero. The ConfigMap used as the policy is supposed to be maintained by users.
|
||||
|
||||
|
||||
### All Changes
|
||||
* Ignore not found error during patching managedFields (#6110, @ywk253100)
|
||||
* Modify new scope resource filters name. (#6089, @blackpiglet)
|
||||
* Make Velero not exits when EnableCSI is on and CSI snapshot not installed (#6062, @blackpiglet)
|
||||
* Restore Services before Clusters (#6057, @ywk253100)
|
||||
* Fixed backup deletion bug related to async operations (#6041, @sseago)
|
||||
* Update Golang version to v1.19 for branch main. (#6039, @blackpiglet)
|
||||
* Fix issue #5972, don't assume errorField as error type when dealing with logger.WithError (#6028, @Lyndon-Li)
|
||||
* distinguish between New and InProgress operations (#6012, @sseago)
|
||||
* Modify golangci.yaml file. Resolve found lint issues. (#6008, @blackpiglet)
|
||||
* Remove Reference of itemsnapshotter (#5997, @reasonerjt)
|
||||
* minor fixes for backup_operations_controller (#5996, @sseago)
|
||||
* RIAv2 async operations controller work (#5993, @sseago)
|
||||
* Follow-on fixes for BIAv2 controller work (#5971, @sseago)
|
||||
* Refactor backup controller based on the controller-runtime framework. (#5969, @qiuming-best)
|
||||
* Fix client wait problem after async operation change, velero backup/restore --wait should check a full list of the terminal status (#5964, @Lyndon-Li)
|
||||
* Fix issue #5935, refactor the logics for backup/restore persistent log, so as to remove the contest to gzip writer (#5956, @Lyndon-Li)
|
||||
* Switch the base image to distroless/base-nossl-debian11 to reduce the CVE triage efforts (#5939, @ywk253100)
|
||||
* Wait for additional items to be ready before restoring current item (#5933, @sseago)
|
||||
* Add configurable server setting for default timeouts (#5926, @eemcmullan)
|
||||
* Add warning/error result to cmd `velero backup describe` (#5916, @allenxu404)
|
||||
* Fix Dependabot alerts. Use 1.18 and 1.19 golang instead of patch image in dockerfile. Add release-1.10 and release-1.9 in Trivy daily scan. (#5911, @blackpiglet)
|
||||
* Update client-go to v0.25.6 (#5907, @kaovilai)
|
||||
* Limit the concurrent number for backup's VolumeSnapshot operation. (#5900, @blackpiglet)
|
||||
* Fix goreleaser issue for resolving tags and updated it's version. (#5899, @anshulahuja98)
|
||||
* This is to fix issue 5881, enhance the PVB tracker in two modes, Track and Taken (#5894, @Lyndon-Li)
|
||||
* Add labels for velero installed namespace to support PSA. (#5873, @blackpiglet)
|
||||
* Add restored resource list in the restore describe command (#5867, @ywk253100)
|
||||
* Add a json output to cmd velero backup describe (#5865, @allenxu404)
|
||||
* Make restore controller adopting the controller-runtime framework. (#5864, @blackpiglet)
|
||||
* Replace k8s.io/apimachinery/pkg/util/clock with k8s.io/utils/clock (#5859, @hezhizhen)
|
||||
* Restore finalizer and managedFields of metadata during the restoration (#5853, @ywk253100)
|
||||
* BIAv2 async operations controller work (#5849, @sseago)
|
||||
* Add secret restore item action to handle service account token secret (#5843, @ywk253100)
|
||||
* Add new resource filters can separate cluster and namespace scope resources. (#5838, @blackpiglet)
|
||||
* Correct PVB/PVR Failed Phase patching during startup (#5828, @kaovilai)
|
||||
* bump up golang net to fix CVE-2022-41721 (#5812, @Lyndon-Li)
|
||||
* Update CRD descriptions for SnapshotVolumes and restorePVs (#5807, @shubham-pampattiwar)
|
||||
* Add mapped selected-node existence check (#5806, @blackpiglet)
|
||||
* Add option "--service-account-name" to install cmd (#5802, @reasonerjt)
|
||||
* Enable staticcheck linter. (#5788, @blackpiglet)
|
||||
* Set Kopia IgnoreUnknownTypes in ErrorHandlingPolicy to True for ignoring backup unknown file type (#5786, @qiuming-best)
|
||||
* Bump up Restic version to 0.15.0 (#5784, @qiuming-best)
|
||||
* Add File system backup related matrics to Grafana dashboard
|
||||
- Add metrics backup_warning_total for record of total warnings
|
||||
- Add metrics backup_last_status for record of last status of the backup (#5779, @allenxu404)
|
||||
* Design for Handling backup of volumes by resources filters (#5773, @qiuming-best)
|
||||
* Add PR container build action, which will not push image. Add GOARM parameter. (#5771, @blackpiglet)
|
||||
* Fix issue 5458, track pod volume backup until the CR is submitted in case it is skipped half way (#5769, @Lyndon-Li)
|
||||
* Fix issue 5226, invalidate the related backup repositories whenever the backup storage info change in BSL (#5768, @Lyndon-Li)
|
||||
* Add Restic builder in Dockerfile, and keep the used built Golang image version in accordance with upstream Restic. (#5764, @blackpiglet)
|
||||
* Fix issue 5043, after the restore pod is scheduled, check if the node-agent pod is running in the same node. (#5760, @Lyndon-Li)
|
||||
* Remove restore controller's redundant client. (#5759, @blackpiglet)
|
||||
* Define itemoperations.json format and update DownloadRequest API (#5752, @sseago)
|
||||
* Add Trivy nightly scan. (#5740, @jxun)
|
||||
* Fix issue 5696, check if the repo is still openable before running the prune and forget operation, if not, try to reconnect the repo (#5715, @Lyndon-Li)
|
||||
* Fix error with Restic backup empty volumes (#5713, @qiuming-best)
|
||||
* new backup and restore phases to support async plugin operations:
|
||||
- WaitingForPluginOperations
|
||||
- WaitingForPluginOperationsPartiallyFailed (#5710, @sseago)
|
||||
* Prevent nil panic on exec restore hooks (#5675, @dymurray)
|
||||
* Fix CVEs scanned by trivy (#5653, @qiuming-best)
|
||||
* Publish backupresults json to enhance error info during backups. (#5576, @anshulahuja98)
|
||||
* RestoreItemAction v2 API implementation (#5569, @sseago)
|
||||
* add new RestoreItemAction of "velero.io/change-image-name" to handle the issue mentioned at #5519 (#5540, @wenterjoy)
|
||||
* BackupItemAction v2 API implementation (#5442, @sseago)
|
||||
* Proposal to separate resource filter into cluster scope and namespace scope (#5333, @blackpiglet)
|
||||
@@ -103,7 +103,7 @@ Also added DownloadTargetKindBackupItemSnapshots for retrieving the signed URL t
|
||||
* Fix CVE-2020-29652 and CVE-2020-26160 (#4274, @ywk253100)
|
||||
* Refine tag-release.sh to align with change in release process (#4185, @reasonerjt)
|
||||
* Fix plugins incompatible issue in upgrade test (#4141, @danfengliu)
|
||||
* Verify group before treating resource as cohabitating (#4126, @sseago)
|
||||
* Verify group before treating resource as cohabiting (#4126, @sseago)
|
||||
* Added ItemSnapshotter plugin definition and plugin framework - addresses #3533.
|
||||
Part of the Upload Progress enhancement (#3533) (#4077, @dsmithuchida)
|
||||
* Add upgrade test in E2E test (#4058, @danfengliu)
|
||||
|
||||
@@ -18,7 +18,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -34,18 +33,16 @@ func main() {
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if done() {
|
||||
fmt.Println("All restic restores are done")
|
||||
err := removeFolder()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println("Done cleanup .velero folder")
|
||||
}
|
||||
return
|
||||
<-ticker.C
|
||||
if done() {
|
||||
fmt.Println("All restic restores are done")
|
||||
err := removeFolder()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println("Done cleanup .velero folder")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +51,7 @@ func main() {
|
||||
// within the .velero/ subdirectory whose name is equal to os.Args[1], or
|
||||
// false otherwise
|
||||
func done() bool {
|
||||
children, err := ioutil.ReadDir("/restores")
|
||||
children, err := os.ReadDir("/restores")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR reading /restores directory: %s\n", err)
|
||||
return false
|
||||
@@ -84,7 +81,7 @@ func done() bool {
|
||||
|
||||
// remove .velero folder
|
||||
func removeFolder() error {
|
||||
children, err := ioutil.ReadDir("/restores")
|
||||
children, err := os.ReadDir("/restores")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -54,6 +54,24 @@ spec:
|
||||
Use DefaultVolumesToFsBackup instead."
|
||||
nullable: true
|
||||
type: boolean
|
||||
excludedClusterScopedResources:
|
||||
description: ExcludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to exclude from the backup. If set to "*", all
|
||||
cluster-scoped resource types are excluded. The default value is
|
||||
empty.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedNamespaceScopedResources:
|
||||
description: ExcludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to exclude from the backup. If set to "*", all
|
||||
namespace-scoped resource types are excluded. The default value
|
||||
is empty.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedNamespaces:
|
||||
description: ExcludedNamespaces contains a list of namespaces that
|
||||
are not included in the backup.
|
||||
@@ -259,6 +277,23 @@ spec:
|
||||
resources should be included for consideration in the backup.
|
||||
nullable: true
|
||||
type: boolean
|
||||
includedClusterScopedResources:
|
||||
description: IncludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to include in the backup. If set to "*", all
|
||||
cluster-scoped resource types are included. The default value is
|
||||
empty, which means only related cluster-scoped resources are included.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaceScopedResources:
|
||||
description: IncludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to include in the backup. The default value
|
||||
is "*".
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaces:
|
||||
description: IncludedNamespaces is a slice of namespace names to include
|
||||
objects from. If empty, all namespaces are included.
|
||||
@@ -273,6 +308,11 @@ spec:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
itemOperationTimeout:
|
||||
description: ItemOperationTimeout specifies the time used to wait
|
||||
for asynchronous BackupItemAction operations The default value is
|
||||
1 hour.
|
||||
type: string
|
||||
labelSelector:
|
||||
description: LabelSelector is a metav1.LabelSelector to filter with
|
||||
when adding individual objects to the backup. If empty or nil, all
|
||||
@@ -392,9 +432,29 @@ spec:
|
||||
"objectname".
|
||||
nullable: true
|
||||
type: object
|
||||
resourcePolicy:
|
||||
description: ResourcePolicy specifies the referenced resource policies
|
||||
that backup should follow
|
||||
properties:
|
||||
apiGroup:
|
||||
description: APIGroup is the group for the resource being referenced.
|
||||
If APIGroup is not specified, the specified Kind must be in
|
||||
the core API group. For any other third-party types, APIGroup
|
||||
is required.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind is the type of resource being referenced
|
||||
type: string
|
||||
name:
|
||||
description: Name is the name of resource being referenced
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
snapshotVolumes:
|
||||
description: SnapshotVolumes specifies whether to take cloud snapshots
|
||||
of any PV's referenced in the set of objects included in the Backup.
|
||||
description: SnapshotVolumes specifies whether to take snapshots of
|
||||
any PV's referenced in the set of objects included in the Backup.
|
||||
nullable: true
|
||||
type: boolean
|
||||
storageLocation:
|
||||
@@ -415,6 +475,20 @@ spec:
|
||||
status:
|
||||
description: BackupStatus captures the current status of a Velero backup.
|
||||
properties:
|
||||
backupItemOperationsAttempted:
|
||||
description: BackupItemOperationsAttempted is the total number of
|
||||
attempted async BackupItemAction operations for this backup.
|
||||
type: integer
|
||||
backupItemOperationsCompleted:
|
||||
description: BackupItemOperationsCompleted is the total number of
|
||||
successfully completed async BackupItemAction operations for this
|
||||
backup.
|
||||
type: integer
|
||||
backupItemOperationsFailed:
|
||||
description: BackupItemOperationsFailed is the total number of async
|
||||
BackupItemAction operations for this backup which ended with an
|
||||
error.
|
||||
type: integer
|
||||
completionTimestamp:
|
||||
description: CompletionTimestamp records the time a backup was completed.
|
||||
Completion time is recorded even on failed backups. Completion time
|
||||
@@ -455,6 +529,10 @@ spec:
|
||||
- New
|
||||
- FailedValidation
|
||||
- InProgress
|
||||
- WaitingForPluginOperations
|
||||
- WaitingForPluginOperationsPartiallyFailed
|
||||
- Finalizing
|
||||
- FinalizingPartiallyFailed
|
||||
- Completed
|
||||
- PartiallyFailed
|
||||
- Failed
|
||||
|
||||
@@ -46,10 +46,13 @@ spec:
|
||||
- BackupLog
|
||||
- BackupContents
|
||||
- BackupVolumeSnapshots
|
||||
- BackupItemSnapshots
|
||||
- BackupItemOperations
|
||||
- BackupResourceList
|
||||
- BackupResults
|
||||
- RestoreLog
|
||||
- RestoreResults
|
||||
- RestoreResourceList
|
||||
- RestoreItemOperations
|
||||
- CSIBackupVolumeSnapshots
|
||||
- CSIBackupVolumeSnapshotContents
|
||||
type: string
|
||||
|
||||
@@ -56,7 +56,7 @@ spec:
|
||||
nullable: true
|
||||
type: array
|
||||
existingResourcePolicy:
|
||||
description: ExistingResourcePolicy specifies the restore behaviour
|
||||
description: ExistingResourcePolicy specifies the restore behavior
|
||||
for the kubernetes resource to be restored
|
||||
nullable: true
|
||||
type: string
|
||||
@@ -238,6 +238,10 @@ spec:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
itemOperationTimeout:
|
||||
description: ItemOperationTimeout specifies the time used to wait
|
||||
for RestoreItemAction operations The default value is 1 hour.
|
||||
type: string
|
||||
labelSelector:
|
||||
description: LabelSelector is a metav1.LabelSelector to filter with
|
||||
when restoring individual objects from the backup. If empty or nil,
|
||||
@@ -355,7 +359,7 @@ spec:
|
||||
type: boolean
|
||||
restorePVs:
|
||||
description: RestorePVs specifies whether to restore all included
|
||||
PVs from snapshot (via the cloudprovider).
|
||||
PVs from snapshot
|
||||
nullable: true
|
||||
type: boolean
|
||||
restoreStatus:
|
||||
@@ -412,6 +416,8 @@ spec:
|
||||
- New
|
||||
- FailedValidation
|
||||
- InProgress
|
||||
- WaitingForPluginOperations
|
||||
- WaitingForPluginOperationsPartiallyFailed
|
||||
- Completed
|
||||
- PartiallyFailed
|
||||
- Failed
|
||||
@@ -432,6 +438,20 @@ spec:
|
||||
due to plugins that return additional related items to restore
|
||||
type: integer
|
||||
type: object
|
||||
restoreItemOperationsAttempted:
|
||||
description: RestoreItemOperationsAttempted is the total number of
|
||||
attempted async RestoreItemAction operations for this restore.
|
||||
type: integer
|
||||
restoreItemOperationsCompleted:
|
||||
description: RestoreItemOperationsCompleted is the total number of
|
||||
successfully completed async RestoreItemAction operations for this
|
||||
restore.
|
||||
type: integer
|
||||
restoreItemOperationsFailed:
|
||||
description: RestoreItemOperationsFailed is the total number of async
|
||||
RestoreItemAction operations for this restore which ended with an
|
||||
error.
|
||||
type: integer
|
||||
startTimestamp:
|
||||
description: StartTimestamp records the time the restore operation
|
||||
was started. The server's time is used for StartTimestamps
|
||||
|
||||
@@ -84,6 +84,24 @@ spec:
|
||||
entirely in future. Use DefaultVolumesToFsBackup instead."
|
||||
nullable: true
|
||||
type: boolean
|
||||
excludedClusterScopedResources:
|
||||
description: ExcludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to exclude from the backup. If set to "*",
|
||||
all cluster-scoped resource types are excluded. The default
|
||||
value is empty.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedNamespaceScopedResources:
|
||||
description: ExcludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to exclude from the backup. If set to "*",
|
||||
all namespace-scoped resource types are excluded. The default
|
||||
value is empty.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedNamespaces:
|
||||
description: ExcludedNamespaces contains a list of namespaces
|
||||
that are not included in the backup.
|
||||
@@ -294,6 +312,24 @@ spec:
|
||||
resources should be included for consideration in the backup.
|
||||
nullable: true
|
||||
type: boolean
|
||||
includedClusterScopedResources:
|
||||
description: IncludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to include in the backup. If set to "*",
|
||||
all cluster-scoped resource types are included. The default
|
||||
value is empty, which means only related cluster-scoped resources
|
||||
are included.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaceScopedResources:
|
||||
description: IncludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to include in the backup. The default value
|
||||
is "*".
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaces:
|
||||
description: IncludedNamespaces is a slice of namespace names
|
||||
to include objects from. If empty, all namespaces are included.
|
||||
@@ -308,6 +344,11 @@ spec:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
itemOperationTimeout:
|
||||
description: ItemOperationTimeout specifies the time used to wait
|
||||
for asynchronous BackupItemAction operations The default value
|
||||
is 1 hour.
|
||||
type: string
|
||||
labelSelector:
|
||||
description: LabelSelector is a metav1.LabelSelector to filter
|
||||
with when adding individual objects to the backup. If empty
|
||||
@@ -427,8 +468,28 @@ spec:
|
||||
simply use "objectname".
|
||||
nullable: true
|
||||
type: object
|
||||
resourcePolicy:
|
||||
description: ResourcePolicy specifies the referenced resource
|
||||
policies that backup should follow
|
||||
properties:
|
||||
apiGroup:
|
||||
description: APIGroup is the group for the resource being
|
||||
referenced. If APIGroup is not specified, the specified
|
||||
Kind must be in the core API group. For any other third-party
|
||||
types, APIGroup is required.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind is the type of resource being referenced
|
||||
type: string
|
||||
name:
|
||||
description: Name is the name of resource being referenced
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
snapshotVolumes:
|
||||
description: SnapshotVolumes specifies whether to take cloud snapshots
|
||||
description: SnapshotVolumes specifies whether to take snapshots
|
||||
of any PV's referenced in the set of objects included in the
|
||||
Backup.
|
||||
nullable: true
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -51,6 +51,19 @@ rules:
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- velero.io
|
||||
resources:
|
||||
- backups/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- velero.io
|
||||
resources:
|
||||
@@ -151,6 +164,32 @@ rules:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- velero.io
|
||||
resources:
|
||||
- restores
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- velero.io
|
||||
resources:
|
||||
- restores/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- velero.io
|
||||
resources:
|
||||
- restorestoragelocations
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- velero.io
|
||||
resources:
|
||||
|
||||
BIN
design/Implemented/AsyncActionFSM.graffle
Normal file
BIN
design/Implemented/AsyncActionFSM.graffle
Normal file
Binary file not shown.
BIN
design/Implemented/AsyncActionFSM.png
Normal file
BIN
design/Implemented/AsyncActionFSM.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
103
design/Implemented/biav2-design.md
Normal file
103
design/Implemented/biav2-design.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Design for BackupItemAction v2 API
|
||||
|
||||
## Abstract
|
||||
This design includes the changes to the BackupItemAction (BIA) api design as required by the [Item Action Progress Monitoring](general-progress-monitoring.md) feature.
|
||||
The BIA v2 interface will have two new methods, and the Execute() return signature will be modified.
|
||||
If there are any additional BIA API changes that are needed in the same Velero release cycle as this change, those can be added here as well.
|
||||
|
||||
## Background
|
||||
This API change is needed to facilitate long-running plugin actions that may not be complete when the Execute() method returns.
|
||||
It is an optional feature, so plugins which don't need this feature can simply return an empty operation ID and the new methods can be no-ops.
|
||||
This will allow long-running plugin actions to continue in the background while Velero moves on to the next plugin, the next item, etc.
|
||||
|
||||
## Goals
|
||||
- Allow for BIA Execute() to optionally initiate a long-running operation and report on operation status.
|
||||
|
||||
## Non Goals
|
||||
- Allowing velero control over when the long-running operation begins.
|
||||
|
||||
|
||||
## High-Level Design
|
||||
As per the [Plugin Versioning](plugin-versioning.md) design, a new BIAv2 plugin `.proto` file will be created to define the GRPC interface.
|
||||
v2 go files will also be created in `plugin/clientmgmt/backupitemaction` and `plugin/framework/backupitemaction`, and a new PluginKind will be created.
|
||||
The velero Backup process will be modified to reference v2 plugins instead of v1 plugins.
|
||||
An adapter will be created so that any existing BIA v1 plugin can be executed as a v2 plugin when executing a backup.
|
||||
|
||||
## Detailed Design
|
||||
|
||||
### proto changes (compiled into golang by protoc)
|
||||
|
||||
The v2 BackupItemAction.proto will be like the current v1 version with the following changes:
|
||||
ExecuteResponse gets a new field:
|
||||
```
|
||||
message ExecuteResponse {
|
||||
bytes item = 1;
|
||||
repeated generated.ResourceIdentifier additionalItems = 2;
|
||||
string operationID = 3;
|
||||
repeated generated.ResourceIdentifier itemsToUpdate = 4;
|
||||
}
|
||||
```
|
||||
The BackupItemAction service gets two new rpc methods:
|
||||
```
|
||||
service BackupItemAction {
|
||||
rpc AppliesTo(BackupItemActionAppliesToRequest) returns (BackupItemActionAppliesToResponse);
|
||||
rpc Execute(ExecuteRequest) returns (ExecuteResponse);
|
||||
rpc Progress(BackupItemActionProgressRequest) returns (BackupItemActionProgressResponse);
|
||||
rpc Cancel(BackupItemActionCancelRequest) returns (google.protobuf.Empty);
|
||||
}
|
||||
```
|
||||
To support these new rpc methods, we define new request/response message types:
|
||||
```
|
||||
message BackupItemActionProgressRequest {
|
||||
string plugin = 1;
|
||||
string operationID = 2;
|
||||
bytes backup = 3;
|
||||
}
|
||||
|
||||
message BackupItemActionProgressResponse {
|
||||
generated.OperationProgress progress = 1;
|
||||
}
|
||||
|
||||
message BackupItemActionCancelRequest {
|
||||
string plugin = 1;
|
||||
string operationID = 2;
|
||||
bytes backup = 3;
|
||||
}
|
||||
|
||||
```
|
||||
One new shared message type will be added, as this will also be needed for v2 RestoreItemAction and VolmeSnapshotter:
|
||||
```
|
||||
message OperationProgress {
|
||||
bool completed = 1;
|
||||
string err = 2;
|
||||
int64 nCompleted = 3;
|
||||
int64 nTotal = 4;
|
||||
string operationUnits = 5;
|
||||
string description = 6;
|
||||
google.protobuf.Timestamp started = 7;
|
||||
google.protobuf.Timestamp updated = 8;
|
||||
}
|
||||
```
|
||||
|
||||
In addition to the two new rpc methods added to the BackupItemAction interface, there is also a new `Name()` method. This one is only actually used internally by Velero to get the name that the plugin was registered with, but it still must be defined in a plugin which implements BackupItemActionV2 in order to implement the interface. It doesn't really matter what it returns, though, as this particular method is not delegated to the plugin via RPC calls. The new (and modified) interface methods for `BackupItemAction` are as follows:
|
||||
```
|
||||
type BackupItemAction interface {
|
||||
...
|
||||
Name() string
|
||||
...
|
||||
Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error)
|
||||
Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error)
|
||||
Cancel(operationID string, backup *api.Backup) error
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
A new PluginKind, `BackupItemActionV2`, will be created, and the backup process will be modified to use this plugin kind.
|
||||
See [Plugin Versioning](plugin-versioning.md) for more details on implementation plans, including v1 adapters, etc.
|
||||
|
||||
|
||||
## Compatibility
|
||||
The included v1 adapter will allow any existing BackupItemAction plugin to work as expected, with an empty operation ID returned from Execute() and no-op Progress() and Cancel() methods.
|
||||
|
||||
## Implementation
|
||||
This will be implemented during the Velero 1.11 development cycle.
|
||||
402
design/Implemented/cluster-scope-resource-filter.md
Normal file
402
design/Implemented/cluster-scope-resource-filter.md
Normal file
@@ -0,0 +1,402 @@
|
||||
# Proposal to add resource filters for backup can distinguish whether resource is cluster-scoped or namespace-scoped.
|
||||
|
||||
- [Proposal to add resource filters for backup can distinguish whether resource is cluster-scoped or namespace-scoped.](#proposal-to-add-resource-filters-for-backup-can-distinguish-whether-resource-is-cluster-scoped-or-namespace-scoped)
|
||||
- [Abstract](#abstract)
|
||||
- [Background](#background)
|
||||
- [Goals](#goals)
|
||||
- [Non Goals](#non-goals)
|
||||
- [High-Level Design](#high-level-design)
|
||||
- [Parameters Rules](#parameters-rules)
|
||||
- [Using scenarios:](#using-scenarios)
|
||||
- [no namespace-scoped resources + some cluster-scoped resources](#no-namespace-scoped-resources--some-cluster-scoped-resources)
|
||||
- [no namespace-scoped resources + all cluster-scoped resources](#no-namespace-scoped-resources--all-cluster-scoped-resources)
|
||||
- [some namespace-scoped resources + no cluster-scoped resources](#some-namespace-scoped-resources--no-cluster-scoped-resources)
|
||||
- [scenario 1](#scenario-1)
|
||||
- [scenario 2](#scenario-2)
|
||||
- [scenario 3](#scenario-3)
|
||||
- [scenario 4](#scenario-4)
|
||||
- [some namespace-scoped resources + only related cluster-scoped resources](#some-namespace-scoped-resources--only-related-cluster-scoped-resources)
|
||||
- [scenario 1](#scenario-1-1)
|
||||
- [scenario 2](#scenario-2-1)
|
||||
- [scenario 3](#scenario-3-1)
|
||||
- [some namespace-scoped resources + some additional cluster-scoped resources](#some-namespace-scoped-resources--some-additional-cluster-scoped-resources)
|
||||
- [scenario 1](#scenario-1-2)
|
||||
- [scenario 2](#scenario-2-2)
|
||||
- [scenario 3](#scenario-3-2)
|
||||
- [scenario 4](#scenario-4-1)
|
||||
- [some namespace-scoped resources + all cluster-scoped resources](#some-namespace-scoped-resources--all-cluster-scoped-resources)
|
||||
- [scenario 1](#scenario-1-3)
|
||||
- [scenario 2](#scenario-2-3)
|
||||
- [scenario 3](#scenario-3-3)
|
||||
- [all namespace-scoped resources + no cluster-scoped resources](#all-namespace-scoped-resources--no-cluster-scoped-resources)
|
||||
- [all namespace-scoped resources + some additional cluster-scoped resources](#all-namespace-scoped-resources--some-additional-cluster-scoped-resources)
|
||||
- [all namespace-scoped resources + all cluster-scoped resources](#all-namespace-scoped-resources--all-cluster-scoped-resources)
|
||||
- [describe command change](#describe-command-change)
|
||||
- [Detailed Design](#detailed-design)
|
||||
- [Alternatives Considered](#alternatives-considered)
|
||||
- [Security Considerations](#security-considerations)
|
||||
- [Compatibility](#compatibility)
|
||||
- [Implementation](#implementation)
|
||||
- [Open Issues](#open-issues)
|
||||
|
||||
## Abstract
|
||||
The current filter (IncludedResources/ExcludedResources + IncludeClusterResources flag) is not enough for some special cases, e.g. all namespace-scoped resources + some kind of cluster-scoped resource and all namespace-scoped resources + cluster-scoped resource excludes.
|
||||
Propose to add a new group of resource filtering parameters, which can distinguish cluster-scoped and namespace-scoped resources.
|
||||
|
||||
## Background
|
||||
There are two sets of resource filters for Velero: `IncludedNamespaces/ExcludedNamespaces` and `IncludedResources/ExcludedResources`.
|
||||
`IncludedResources` means only including the resource types specified in the parameter. Both cluster-scoped and namespace-scoped resources are handled in this parameter by now.
|
||||
The k8s resources are separated into cluster-scoped and namespace-scoped.
|
||||
As a result, it's hard to include all resources in one group and only including specified resource in the other group.
|
||||
|
||||
## Goals
|
||||
- Make Velero can support more complicated namespace-scoped and cluster-scoped resources filtering scenarios in backup.
|
||||
|
||||
## Non Goals
|
||||
- Enrich the resource filtering rules, for example, advanced PV filtering and filtering by resource names.
|
||||
|
||||
|
||||
## High-Level Design
|
||||
Four new parameters are added into command `velero backup create`: `--include-cluster-scoped-resources`, `--exclude-cluster-scoped-resources`, `--include-namespace-scoped-resources` and `--exclude-namespace-scoped-resources`.
|
||||
`--include-cluster-scoped-resources` and `--exclude-cluster-scoped-resources` are used to filter cluster-scoped resources included or excluded in backup per resource type.
|
||||
`--include-namespace-scoped-resources` and `--exclude-namespace-scoped-resources` are used to filter namespace-scoped resources included or excluded in backup per resource type.
|
||||
Restore and other code pieces also use resource filtering will be handled in future releases.
|
||||
|
||||
### Parameters Rules
|
||||
|
||||
* `--include-cluster-scoped-resources`, `--include-namespace-scoped-resources`, `--exclude-cluster-scoped-resources` and `--exclude-namespace-scoped-resources` valid value include `*` and comma separated string. Each element of the CSV string should a k8s resource name. The format should be `resource.group`, such as `storageclasses.storage.k8s.io.`.
|
||||
|
||||
* `--include-cluster-scoped-resources`, `--include-namespace-scoped-resources`, `--exclude-cluster-scoped-resources` and `--exclude-namespace-scoped-resources` parameters are mutual exclusive with `--include-cluster-resources`, `--include-resources` and `--exclude-resources` parameters. If both sets of parameters are provisioned, validation failure should be returned.
|
||||
|
||||
* `--include-cluster-scoped-resources` and `--exclude-cluster-scoped-resources` should only contain cluster-scoped resource type names. If namespace-scoped resource type names are included, they are ignored.
|
||||
|
||||
* If there are conflicts between `--include-cluster-scoped-resources` and `--exclude-cluster-scoped-resources` specified resources type lists, `--exclude-cluster-scoped-resources` parameter has higher priority.
|
||||
|
||||
* `--include-namespace-scoped-resources` and `--exclude-namespace-scoped-resources` should only contain namespace-scoped resource type names. If cluster-scoped resource type names are included, they are ignored.
|
||||
|
||||
* If there are conflicts between `--include-namespace-scoped-resources` and `--exclude-namespace-scoped-resources` specified resources type lists, `--exclude-namespace-scoped-resources` parameter has higher priority.
|
||||
|
||||
* If `--include-namespace-scoped-resources` is not present, it means all namespace-scoped resources are included per resource type.
|
||||
|
||||
* If both `--include-cluster-scoped-resources` and `--exclude-cluster-scoped-resources` are not present, it means no additional cluster-scoped resource is included per resource type, just as the existing `--include-cluster-resources` parameter not setting value. Cluster-scoped resources are related to the namespace-scoped resources, which means those are returned in the namespace-scoped resources' BackupItemAction's result AdditionalItems array, are still included in backup by default. Taking backing up PVC scenario as an example, PVC is namespace-scoped, PV is cluster-scoped. PVC's BIA will include PVC related PV into backup too.
|
||||
|
||||
### Using scenarios:
|
||||
Please notice, if the scenario give the example of using old filtering parameters (`--include-cluster-resources`, `--include-resources` and `--exclude-resources`), that means the old parameters also work for this case. If old parameters example is not given, that means they don't work for this scenario, only new parameters (`--include-cluster-scoped-resources`, `--include-namespace-scoped-resources`, `--exclude-cluster-scoped-resources` and `--exclude-namespace-scoped-resources`) work.
|
||||
|
||||
#### no namespace-scoped resources + some cluster-scoped resources
|
||||
The following command means backup no namespace-scoped resources and some cluster-scoped resources.
|
||||
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--exclude-namespace-scoped-resources=*
|
||||
--include-cluster-scoped-resources=storageclass
|
||||
```
|
||||
|
||||
#### no namespace-scoped resources + all cluster-scoped resources
|
||||
The following command means backup no namespace-scoped resources and all cluster-scoped resources.
|
||||
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--exclude-namespace-scoped-resources=*
|
||||
--include-cluster-scoped-resources=*
|
||||
```
|
||||
|
||||
#### some namespace-scoped resources + no cluster-scoped resources
|
||||
##### scenario 1
|
||||
The following commands mean backup all resources in namespaces default and kube-system, and no cluster-scoped resources.
|
||||
|
||||
Example of new parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=default,kube-system
|
||||
--exclude-cluster-scoped-resources=*
|
||||
```
|
||||
|
||||
Example of old parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=default,kube-system
|
||||
--include-cluster-resources=false
|
||||
```
|
||||
##### scenario 2
|
||||
The following commands mean backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in all namespaces, and no cluster-scoped resources. Although PVC's related PV should be included, due to no cluster-scoped resources are included, so they are ruled out too.
|
||||
|
||||
Example of new parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespace-scoped-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
|
||||
--exclude-cluster-scope-resources=*
|
||||
```
|
||||
|
||||
Example of old parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
|
||||
--include-cluster-resources=false
|
||||
```
|
||||
##### scenario 3
|
||||
The following commands mean backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in namespace default and kube-system, and no cluster-scoped resources. Although PVC's related PV should be included, due to no cluster-scoped resources are included, so they are ruled out too.
|
||||
|
||||
Example of new parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=default,kube-system
|
||||
--include-namespace-scoped-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
|
||||
--exclude-cluster-scope-resources=*
|
||||
```
|
||||
|
||||
Example of old parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=default,kube-system
|
||||
--include-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
|
||||
--include-cluster-resources=false
|
||||
```
|
||||
##### scenario 4
|
||||
The following commands mean backup all resources except Ingress type resources in all namespaces, and no cluster-scoped resources.
|
||||
|
||||
Example of new parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--exclude-namespace-scoped-resources=ingress
|
||||
--exclude-cluster-scoped-resources=*
|
||||
```
|
||||
|
||||
Example of old parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--exclude-resources=ingress
|
||||
--include-cluster-resources=false
|
||||
```
|
||||
|
||||
#### some namespace-scoped resources + only related cluster-scoped resources
|
||||
##### scenario 1
|
||||
This means backup all resources in namespaces default and kube-system, and related cluster-scoped resources.
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=default,kube-system
|
||||
```
|
||||
|
||||
##### scenario 2
|
||||
This means backup pods and configmaps in namespaces default and kube-system, and related cluster-scoped resources.
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=default,kube-system
|
||||
--include-namespace-scoped-resources=pods,configmaps
|
||||
```
|
||||
|
||||
##### scenario 3
|
||||
This means backup all resources except Ingress type resources in all namespaces, and related cluster-scoped resources.
|
||||
|
||||
Example of new parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--exclude-namespace-scoped-resources=ingress
|
||||
```
|
||||
|
||||
Example of old parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--exclude-resources=ingress
|
||||
```
|
||||
|
||||
#### some namespace-scoped resources + some additional cluster-scoped resources
|
||||
##### scenario 1
|
||||
This means backup all resources in namespace in default, kube-system, and related cluster-scoped resources, plus all StorageClass resources.
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=default,kube-system
|
||||
--include-cluster-scoped-resources=storageclass
|
||||
```
|
||||
|
||||
##### scenario 2
|
||||
This means backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in all namespaces, and related cluster-scoped resources, plus all StorageClass resources, and PVC related PV.
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespace-scoped-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
|
||||
--include-cluster-scoped-resources=storageclass
|
||||
```
|
||||
|
||||
##### scenario 3
|
||||
This means backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in default and kube-system namespaces, and related cluster-scoped resources, plus all StorageClass resources, and PVC related PV.
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespace-scoped-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
|
||||
--include-namespaces=default,kube-system
|
||||
--include-cluster-scoped-resources=storageclass
|
||||
```
|
||||
|
||||
##### scenario 4
|
||||
This means backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in default and kube-system namespaces, and related cluster-scoped resources, plus all cluster-scoped resources except StorageClass type resources.
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespace-scoped-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
|
||||
--include-namespaces=default,kube-system
|
||||
--exclude-cluster-scoped-resources=storageclass
|
||||
```
|
||||
|
||||
#### some namespace-scoped resources + all cluster-scoped resources
|
||||
##### scenario 1
|
||||
The following commands mean backup all resources in namespace in default, kube-system, and all cluster-scoped resources.
|
||||
|
||||
Example of new parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=default,kube-system
|
||||
--include-cluster-scoped-resources=*
|
||||
```
|
||||
|
||||
Example of old parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=default,kube-system
|
||||
--include-cluster-resources=true
|
||||
```
|
||||
|
||||
##### scenario 2
|
||||
This means backup Deployment, Service, Endpoint, Pod and ReplicaSet resources in all namespaces, and all cluster-scoped resources.
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespace-scoped-resources=deployment,service,endpoint,pod,replicaset
|
||||
--include-cluster-scoped-resources=*
|
||||
```
|
||||
|
||||
##### scenario 3
|
||||
This means backup Deployment, Service, Endpoint, Pod and ReplicaSet resources in default and kube-system namespaces, and all cluster-scoped resources.
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=default,kube-system
|
||||
--include-namespace-scoped-resources=deployment,service,endpoint,pod,replicaset
|
||||
--include-cluster-scoped-resources=*
|
||||
```
|
||||
|
||||
#### all namespace-scoped resources + no cluster-scoped resources
|
||||
The following commands all mean backup all namespace-scoped resources and no cluster-scoped resources.
|
||||
|
||||
Example of new parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--exclude-cluster-scoped-resources=*
|
||||
```
|
||||
|
||||
Example of old parameters:
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-cluster-resources=false
|
||||
```
|
||||
|
||||
#### all namespace-scoped resources + some additional cluster-scoped resources
|
||||
This command means backup all namespace-scoped resources, and related cluster-scoped resources, plus all PersistentVolume resources.
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-namespaces=*
|
||||
--include-cluster-scoped-resources=persistentvolume
|
||||
```
|
||||
|
||||
#### all namespace-scoped resources + all cluster-scoped resources
|
||||
The following commands have the same meaning: backup all namespace-scoped resources, and all cluster-scoped resources.
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-cluster-scoped-resources=*
|
||||
```
|
||||
|
||||
``` bash
|
||||
velero backup create <backup-name>
|
||||
--include-cluster-resources=true
|
||||
```
|
||||
|
||||
#### describe command change
|
||||
In `velero backup describe` command, the four new parameters should be outputted too.
|
||||
``` bash
|
||||
velero backup describe <backup-name>
|
||||
......
|
||||
|
||||
Namespaces:
|
||||
Included: ns2
|
||||
Excluded: <none>
|
||||
|
||||
Resources:
|
||||
Included cluster-scoped: StorageClass,PersistentVolume
|
||||
Excluded cluster-scoped: <none>
|
||||
Included namespace-scoped: default
|
||||
Excluded namespace-scoped: <none>
|
||||
......
|
||||
```
|
||||
|
||||
**Note:** `velero restore` command doesn't support those four new parameter in Velero v1.11, but `velero schedule` supports the four new parameters through backup specification.
|
||||
|
||||
## Detailed Design
|
||||
With adding `IncludedNamespaceScopedResources`, `ExcludedNamespaceScopedResources`, `IncludedClusterScopedResources` and `ExcludedClusterScopedResources`, the `BackupSpec` looks like:
|
||||
``` go
|
||||
type BackupSpec struct {
|
||||
......
|
||||
// IncludedResources is a slice of resource names to include
|
||||
// in the backup. If empty, all resources are included.
|
||||
// +optional
|
||||
// +nullable
|
||||
IncludedResources []string `json:"includedResources,omitempty"`
|
||||
|
||||
// ExcludedResources is a slice of resource names that are not
|
||||
// included in the backup.
|
||||
// +optional
|
||||
// +nullable
|
||||
ExcludedResources []string `json:"excludedResources,omitempty"`
|
||||
|
||||
// IncludeClusterResources specifies whether cluster-scoped resources
|
||||
// should be included for consideration in the backup.
|
||||
// +optional
|
||||
// +nullable
|
||||
IncludeClusterResources *bool `json:"includeClusterResources,omitempty"`
|
||||
|
||||
// IncludedClusterScopedResources is a slice of cluster-scoped
|
||||
// resource type names to include in the backup.
|
||||
// If set to "*", all cluster scope resource types are included.
|
||||
// The default value is empty, which means only related cluster
|
||||
// scope resources are included.
|
||||
// +optional
|
||||
// +nullable
|
||||
IncludedClusterScopedResources []string `json:"includedClusterScopedResources,omitempty"`
|
||||
|
||||
// ExcludedClusterScopedResources is a slice of cluster-scoped
|
||||
// resource type names to exclude from the backup.
|
||||
// If set to "*", all cluster scope resource types are excluded.
|
||||
// +optional
|
||||
// +nullable
|
||||
ExcludedClusterScopedResources []string `json:"excludedClusterScopedResources,omitempty"`
|
||||
|
||||
// IncludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
// resource type names to include in the backup.
|
||||
// The default value is "*".
|
||||
// +optional
|
||||
// +nullable
|
||||
IncludedNamespaceScopedResources []string `json:"includedNamespaceScopedResources,omitempty"`
|
||||
|
||||
// ExcludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
// resource type names to exclude from the backup.
|
||||
// If set to "*", all namespace scope resource types are excluded.
|
||||
// +optional
|
||||
// +nullable
|
||||
ExcludedNamespaceScopedResources []string `json:"excludedNamespaceScopedResources,omitempty"`
|
||||
......
|
||||
}
|
||||
```
|
||||
|
||||
## Alternatives Considered
|
||||
Proposal from Jibu Data [Issue 5120](https://github.com/vmware-tanzu/velero/issues/5120#issue-1304534563)
|
||||
|
||||
## Security Considerations
|
||||
No security impact.
|
||||
|
||||
## Compatibility
|
||||
The four new parameters cannot be mixed with existing resource filter parameters: `IncludedResources`, `ExcludedResources` and `IncludeClusterResources`.
|
||||
If the new parameters and old parameters both appears in command line, or are specified in backup spec, the command line and the backup should fail.
|
||||
|
||||
## Implementation
|
||||
This change should be included into Velero v1.11.
|
||||
New parameters will coexist with `IncludedResources`, `ExcludedResources` and `IncludeClusterResources`.
|
||||
Plan to deprecate `IncludedResources`, `ExcludedResources` and `IncludeClusterResources` in future releases, but also open to the community's feedback.
|
||||
|
||||
## Open Issues
|
||||
`LabelSelector/OrLabelSelectors` apply to namespace-scoped resources.
|
||||
It may be reasonable to make them also working on cluster-scoped resources.
|
||||
An issue is created to trace this topic [resource label selector not work for cluster-scoped resources](https://github.com/vmware-tanzu/velero/issues/5787)
|
||||
579
design/Implemented/general-progress-monitoring.md
Normal file
579
design/Implemented/general-progress-monitoring.md
Normal file
@@ -0,0 +1,579 @@
|
||||
# Plugin Progress Monitoring
|
||||
|
||||
This is intended as a replacement for the previously-approved Upload Progress Monitoring design
|
||||
([Upload Progress Monitoring](upload-progress.md)) in order to expand the supported use cases beyond
|
||||
snapshot uploads to include what was previously called Async Backup/Restore Item Actions. This
|
||||
updated design should handle the combined set of use cases for those previously separate designs.
|
||||
|
||||
Volume snapshotter plug-in are used by Velero to take snapshots of persistent volume contents.
|
||||
Depending on the underlying storage system, those snapshots may be available to use immediately,
|
||||
they may be uploaded to stable storage internally by the plug-in or they may need to be uploaded after
|
||||
the snapshot has been taken. We would like for Velero to continue on to the next part of the backup as quickly
|
||||
as possible but we would also like the backup to not be marked as complete until it is a usable backup. We'd also
|
||||
eventually like to bring the control of upload under the control of Velero and allow the user to make decisions
|
||||
about the ultimate destination of backup data independent of the storage system they're using.
|
||||
|
||||
We would also like any internal or third party Backup or Restore Item Action to have the option of
|
||||
making use of this same ability to run some external process without blocking the current backup or
|
||||
restore operation. Beyond Volume Snapshotters, this is also needed for data mover operations on both
|
||||
backup and restore, and potentially useful for other third party operations -- for example
|
||||
in-cluster registry image backup or restore could make use of this feature in a third party plugin).
|
||||
|
||||
### Glossary
|
||||
- <b>BIA</b>: BackupItemAction
|
||||
- <b>RIA</b>: RestoreItemAction
|
||||
|
||||
## Examples
|
||||
- AWS - AWS snapshots return quickly, but are then uploaded in the background and cannot be used until EBS moves
|
||||
the data into S3 internally.
|
||||
|
||||
- vSphere - The vSphere plugin takes a local snapshot and then the vSphere plugin uploads the data to S3. The local
|
||||
snapshot is usable before the upload completes.
|
||||
|
||||
- Restic - Does not go through the volume snapshot path. Restic backups will block Velero progress
|
||||
until completed. However, with the more generalized approach in the revised design, restic/kopia
|
||||
backup and restore *could* make use of this framework if their actions are refactored as
|
||||
Backup/RestoreItemActions.
|
||||
|
||||
- Data Movers
|
||||
- Data movers are asynchronous processes executed inside backup/restore item actions that applies to a specific kubernetes resources. A common use case for data mover is to backup/restore PVCs whose data we want to move to some form of backup storage outside of using velero kopia/restic implementations.
|
||||
- Workflow
|
||||
- User takes velero backup of PVC A
|
||||
- BIA plugin applies to PVCs with compatible storage driver
|
||||
- BIA plugin triggers data mover
|
||||
- Most common use case would be for the plugin action to create a new CR which would
|
||||
trigger an external controller action
|
||||
- Another possible use case would be for the plugin to run its own async action in a
|
||||
goroutine, although this would be less resilient to plugin container restarts.
|
||||
- BIA plugin returns
|
||||
- Velero backup process continues
|
||||
- Main velero backup process monitors running BIA threads via gRPC to determine if process is done and healthy
|
||||
|
||||
|
||||
## Primary changes from the original Upload Progress Monitoring design
|
||||
|
||||
The most fundamental change here is that rather than proposing a new special-purpose
|
||||
SnapshotItemAction, the existing BackupItemAction plugin will be modified to accommodate an optional
|
||||
snapshot (or other item operation) ID return. The primary reasons for this change are as follows:
|
||||
|
||||
1. The intended scope has moved beyond snapshot processing, so it makes sense to support
|
||||
asynchronous operations in other backup or restore item actions.
|
||||
|
||||
2. We expect to have plugin API versioning implemented in Velero 1.10, making it feasible to
|
||||
implement changes in the existing plugin APIs now.
|
||||
|
||||
3. We will need this feature on both backup and restore, meaning that if we took the "new plugin
|
||||
type" approach, we'd need two new plugin types.
|
||||
|
||||
4. Other than the snapshot/operation ID return, the rest of the plugin processing is identical to
|
||||
Backup/RestoreItemActions. With separate plugin types, we'd have to repeat all of that logic
|
||||
(including dealing with additional items, etc.) twice.
|
||||
|
||||
The other major change is that we will be applying this to both backups and restores, although the
|
||||
Volume Snapshotter use case only needs this on backup. This means that everything we're doing around
|
||||
backup phase and workflow will also need to be done for restore.
|
||||
|
||||
Then there are various minor changes around terminology to make things more generic. Instead of
|
||||
"snapshotID", we'll have "operationID" (which for volume snapshotters will be a snapshot
|
||||
ID).
|
||||
|
||||
## Goals
|
||||
|
||||
- Enable monitoring of backup/restore item action operations that continue after snapshotting and other operations have completed
|
||||
- Keep non-usable backups and restores (upload/persistence has not finished, etc.) from appearing as completed
|
||||
- Make use of plugin API versioning functionality to manage changes to Backup/RestoreItemAction interfaces
|
||||
- Enable vendors to plug their own data movers into velero using BIA/RIA plugins
|
||||
|
||||
## Non-goals
|
||||
- Today, Velero is unable to recover from an in progress backup when the velero server crashes (pod is deleted). This has an impact on running asynchronous processes, but it’s not something we intend to solve in this design.
|
||||
|
||||
## Models
|
||||
|
||||
### Internal configuration and management
|
||||
In this model, movement of the snapshot to stable storage is under the control of the snapshot
|
||||
plug-in. Decisions about where and when the snapshot gets moved to stable storage are not
|
||||
directly controlled by Velero. This is the model for the current VolumeSnapshot plugins.
|
||||
|
||||
### Velero controlled management
|
||||
In this model, the snapshot is moved to external storage under the control of Velero. This
|
||||
enables Velero to move data between storage systems. This also allows backup partners to use
|
||||
Velero to snapshot data and then move the data into their backup repository.
|
||||
|
||||
## Backup and Restore phases
|
||||
|
||||
Velero currently has backup/restore phases "InProgress" and "Completed". A backup moves to the
|
||||
Completed phase when all of the volume snapshots have completed and the Kubernetes metadata has been
|
||||
written into the object store. However, the actual data movement may be happening in the background
|
||||
after the backup has been marked "Completed". The backup is not actually a stable backup until the
|
||||
data has been persisted properly. In some cases (e.g. AWS) the backup cannot be restored from until
|
||||
the snapshots have been persisted.
|
||||
|
||||
Once the snapshots have been taken, however, it is possible for additional backups or restores (as
|
||||
long as they don't use not-yet-completed backups) to be made without interference. Waiting until
|
||||
all data has been moved before starting the next backup will slow the progress of the system without
|
||||
adding any actual benefit to the user.
|
||||
|
||||
New backup/restore phases, "WaitingForPluginOperations" and
|
||||
"WaitingForPluginOperationsPartiallyFailed" will be introduced. When a backup or restore has
|
||||
entered one of these phases, Velero is free to start another backup/restore. The backup/restore
|
||||
will remain in the "WaitingForPluginOperations" phase until all BIA/RIA operations have completed
|
||||
(for example, for a volume snapshotter, until all data has been successfully moved to persistent
|
||||
storage). The backup/restore will not fail once it reaches this phase, although an error return
|
||||
from a plugin could cause a backup or restore to move to "PartiallyFailed". If the backup is
|
||||
deleted (cancelled), the plug-ins will attempt to delete the snapshots and stop the data movement -
|
||||
this may not be possible with all storage systems.
|
||||
|
||||
In addition, for backups (but not restores), there will also be two additional phases, "Finalizing"
|
||||
and "FinalizingPartiallyFailed", which will handle any steps required after plugin operations have
|
||||
all completed. Initially, this will just include adding any required resources to the backup that
|
||||
might have changed during asynchronous operation execution, although eventually other cleanup
|
||||
actions could be added to this phase.
|
||||
|
||||
### State progression
|
||||
|
||||

|
||||
### New
|
||||
When a backup/restore request is initially created, it is in the "New" phase.
|
||||
|
||||
The next state is either "InProgress" or "FailedValidation"
|
||||
|
||||
### FailedValidation
|
||||
If the backup/restore request is incorrectly formed, it goes to the "FailedValidation" phase and
|
||||
terminates
|
||||
|
||||
### InProgress
|
||||
When work on the backup/restore begins, it moves to the "InProgress" phase. It remains in the
|
||||
"InProgress" phase until all pre/post execution hooks have been executed, all snapshots have been
|
||||
taken and the Kubernetes metadata and backup/restore info is safely written to the object store
|
||||
plug-in.
|
||||
|
||||
In the current implementation, Restic backups will move data during the "InProgress" phase. In the
|
||||
future, it may be possible to combine a snapshot with a Restic (or equivalent) backup which would
|
||||
allow for data movement to be handled in the "WaitingForPluginOperations" phase,
|
||||
|
||||
The next phase would be "WaitingForPluginOperations" for backups or restores which have unfinished
|
||||
asynchronous plugin operations and no errors so far, "WaitingForPluginOperationsPartiallyFailed" for
|
||||
backups or restores which have unfinished asynchronous plugin operations at least one error,
|
||||
"Completed" for restores with no unfinished asynchronous plugin operations and no errors,
|
||||
"PartiallyFailed" for restores with no unfinished asynchronous plugin operations and at least one
|
||||
error, "Finalizing" for backups with no unfinished asynchronous plugin operations and no errors,
|
||||
"FinalizingPartiallyFailed" for backups with no unfinished asynchronous plugin operations and at
|
||||
least one error, or "PartiallyFailed". Backups/restores which would have a final phase of
|
||||
"Completed" or "PartiallyFailed" may move to the "WaitingForPluginOperations" or
|
||||
"WaitingForPluginOperationsPartiallyFailed" state. A backup/restore which will be marked "Failed"
|
||||
will go directly to the "Failed" phase. Uploads may continue in the background for snapshots that
|
||||
were taken by a "Failed" backup/restore, but no progress will not be monitored or updated. If there
|
||||
are any operations in progress when a backup is moved to the "Failed" phase (although with the
|
||||
current workflow, that shouldn't happen), Cancel() should be called on these operations. When a
|
||||
"Failed" backup is deleted, all snapshots will be deleted and at that point any uploads still in
|
||||
progress should be aborted.
|
||||
|
||||
### WaitingForPluginOperations (new)
|
||||
The "WaitingForPluginOperations" phase signifies that the main part of the backup/restore, including
|
||||
snapshotting has completed successfully and uploading and any other asynchronous BIA/RIA plugin
|
||||
operations are continuing. In the event of an error during this phase, the phase will change to
|
||||
WaitingForPluginOperationsPartiallyFailed. On success, the phase changes to
|
||||
"Finalizing" for backups and "Completed" for restores. Backups cannot be
|
||||
restored from when they are in the WaitingForPluginOperations state.
|
||||
|
||||
### WaitingForPluginOperationsPartiallyFailed (new)
|
||||
The "WaitingForPluginOperationsPartiallyFailed" phase signifies that the main part of the
|
||||
backup/restore, including snapshotting has completed, but there were partial failures either during
|
||||
the main part or during any async operations, including snapshot uploads. Backups cannot be
|
||||
restored from when they are in the WaitingForPluginOperationsPartiallyFailed state.
|
||||
|
||||
### Finalizing (new)
|
||||
The "Finalizing" phase signifies that asynchronous backup operations have all completed successfully
|
||||
and Velero is currently backing up any resources indicated by asynchronous plugins as items to back
|
||||
up after operations complete. Once this is done, the phase changes to Completed. Backups cannot be
|
||||
restored from when they are in the Finalizing state.
|
||||
|
||||
### FinalizingPartiallyFailed (new)
|
||||
|
||||
The "FinalizingPartiallyFailed" phase signifies that, for a backup which had errors during initial
|
||||
processing or asynchronous plugin operation, asynchronous backup operations have all completed and
|
||||
Velero is currently backing up any resources indicated by asynchronous plugins as items to back up
|
||||
after operations complete. Once this is done, the phase changes to PartiallyFailed. Backups cannot
|
||||
be restored from when they are in the FinalizingPartiallyFailed state.
|
||||
|
||||
### Failed
|
||||
When a backup/restore has had fatal errors it is marked as "Failed" Backups in this state cannot be
|
||||
restored from.
|
||||
|
||||
### Completed
|
||||
The "Completed" phase signifies that the backup/restore has completed, all data has been transferred
|
||||
to stable storage (or restored to the cluster) and any backup in this state is ready to be used in a
|
||||
restore. When the Completed phase has been reached for a backup it is safe to remove any of the
|
||||
items that were backed up.
|
||||
|
||||
### PartiallyFailed
|
||||
The "PartiallyFailed" phase signifies that the backup/restore has completed and at least part of the
|
||||
backup/restore is usable. Restoration from a PartiallyFailed backup will not result in a complete
|
||||
restoration but pieces may be available.
|
||||
|
||||
## Workflow
|
||||
|
||||
When a Backup or Restore Action is executed, any BackupItemAction, RestoreItemAction, or
|
||||
VolumeSnapshot plugins will return operation IDs (snapshot IDs or other plugin-specific
|
||||
identifiers). The plugin should be able to provide status on the progress for the snapshot and
|
||||
handle cancellation of the operation/upload if the snapshot is deleted. If the plugin is restarted,
|
||||
the operation ID should remain valid.
|
||||
|
||||
When all snapshots have been taken and Kubernetes resources have been persisted to the ObjectStorePlugin
|
||||
the backup will either have fatal errors or will be at least partially usable.
|
||||
|
||||
If the backup/restore has fatal errors it will move to the "Failed" state and finish. If a
|
||||
backup/restore fails, the upload or other operation will not be cancelled but it will not be
|
||||
monitored either. For backups in any phase, all snapshots will be deleted when the backup is
|
||||
deleted. Plugins will cancel any data movement or other operations and remove snapshots and other
|
||||
associated resources when the VolumeSnapshotter DeleteSnapshot method or DeleteItemAction Execute
|
||||
method is called.
|
||||
|
||||
Velero will poll the plugins for status on the operations when the backup/restore exits the
|
||||
"InProgress" phase and has no fatal errors.
|
||||
|
||||
If any operations are not complete, the backup/restore will move to either WaitingForPluginOperations
|
||||
or WaitingForPluginOperationsPartiallyFailed or Failed.
|
||||
|
||||
Post-snapshot and other operations may take a long time and Velero and its plugins may be restarted
|
||||
during this time. Once a backup/restore has moved into the WaitingForPluginOperations or
|
||||
WaitingForPluginOperationsPartiallyFailed phase, another backup/restore may be started.
|
||||
|
||||
While in the WaitingForPluginOperations or WaitingForPluginOperationsPartiallyFailed phase, the
|
||||
snapshots and item actions will be periodically polled. When all of the snapshots and item actions
|
||||
have reported success, restores will move directly to the Completed or PartiallyFailed phase, and
|
||||
backups will move to the Finalizing or FinalizingPartiallyFailed phase, depending on whether the
|
||||
backup/restore was in the WaitingForPluginOperations or WaitingForPluginOperationsPartiallyFailed
|
||||
phase.
|
||||
|
||||
While in the Finalizing or FinalizingPartiallyFailed phase, Velero will update the backup with any
|
||||
resources indicated by plugins that they must be added to the backup after operations are completed,
|
||||
and then the backup will move to the Completed or PartiallyFailed phase, depending on whether there
|
||||
are any backup errors.
|
||||
|
||||
The Backup resources will be written to object storage at the time the backup leaves the InProgress
|
||||
phase, but it will not be synced to other clusters (or usable for restores in the current cluster)
|
||||
until the backup has entered a final phase: Completed, Failed or PartiallyFailed. During the
|
||||
Finalizing phases, a the backup resources will be updated with any required resources related to
|
||||
asynchronous plugins.
|
||||
|
||||
## Reconciliation of InProgress backups
|
||||
|
||||
InProgress backups will not have a `velero-backup.json` present in the object store. During
|
||||
reconciliation, backups which do not have a `velero-backup.json` object in the object store will be
|
||||
ignored.
|
||||
|
||||
## Plug-in API changes
|
||||
|
||||
### OperationProgress struct
|
||||
|
||||
type OperationProgress struct {
|
||||
Completed bool // True when the operation has completed, either successfully or with a failure
|
||||
Err string // Set when the operation has failed
|
||||
NCompleted, NTotal int64 // Quantity completed so far and the total quantity associated with the operation in operationUnits
|
||||
// For data mover and volume snapshotter use cases, this would be in bytes
|
||||
// On successful completion, completed and total should be the same.
|
||||
OperationUnits string // Units represented by completed and total -- for data mover and item
|
||||
// snapshotters, this will usually be bytes.
|
||||
Description string // Optional description of operation progress
|
||||
Started, Updated time.Time // When the upload was started and when the last update was seen. Not all
|
||||
// systems retain when the upload was begun, return Time 0 (time.Unix(0, 0))
|
||||
// if unknown.
|
||||
}
|
||||
|
||||
### VolumeSnapshotter changes
|
||||
|
||||
Two new methods will be added to the VolumeSnapshotter interface:
|
||||
|
||||
Progress(snapshotID string) (OperationProgress, error)
|
||||
Cancel(snapshotID string) (error)
|
||||
|
||||
Progress will report the current status of a snapshot upload. This should be callable at
|
||||
any time after the snapshot has been taken. In the event a plug-in is restarted, if the operationID
|
||||
(snapshot ID) continues to be valid it should be possible to retrieve the progress.
|
||||
|
||||
`error` is set if there is an issue retrieving progress. If the snapshot is has encountered an
|
||||
error during the upload, the error should be returned in OperationProgress and error should be nil.
|
||||
|
||||
### BackupItemAction and RestoreItemAction plug-in changes
|
||||
|
||||
Currently CSI snapshots and the Velero Plug-in for vSphere are implemented as BackupItemAction
|
||||
plugins. While the majority of BackupItemAction plugins do not take snapshots or upload data, this
|
||||
functionality is useful for any longstanding plugin operation managed by an external
|
||||
process/controller so we will modify BackupItemAction and RestoreItemAction to optionally return an
|
||||
operationID in addition to the modified item.
|
||||
|
||||
Velero can attempt to cancel an operation by calling the Cancel API call on the BIA/RIA. The plugin
|
||||
can then take any appropriate action as needed. Cancel will be called for unfinished operations on
|
||||
backup deletion, and possibly reaching timeout. Cancel is not intended to be used to delete/remove
|
||||
the results of completed actions and will have no effect on a completed action. Cancel has no return
|
||||
value apart from the standard Error return, but this should only be used for unexpected
|
||||
failures. Under normal operations, Cancel will simply return a nil error (and nothing else), whether
|
||||
or not the plugin is able to cancel the operation.
|
||||
|
||||
_AsyncOperationsNotSupportedError_ should only be returned (by Progress) if the
|
||||
Backup/RestoreItemAction plugin should not be handling the item. If the Backup/RestoreItemAction
|
||||
plugin should handle the item but, for example, the item/snapshot ID cannot be found to report
|
||||
progress, Progress will return an InvalidOperationIDError error rather than a populated
|
||||
OperationProgress struct. If the item action does not start an asynchronous operation, then
|
||||
operationID will be empty.
|
||||
|
||||
Three new methods will be added to the BackupItemAction interface, and the Execute() return signature
|
||||
will be modified:
|
||||
|
||||
// Name returns the name of this BIA. Plugins which implement this interface must defined Name,
|
||||
// but its content is unimportant, as it won't actually be called via RPC. Velero's plugin infrastructure
|
||||
// will implement this directly rather than delegating to the RPC plugin in order to return the name
|
||||
// that the plugin was registered under. The plugins must implement the method to complete the interface.
|
||||
Name() string
|
||||
// Execute allows the BackupItemAction to perform arbitrary logic with the item being backed up,
|
||||
// including mutating the item itself prior to backup. The item (unmodified or modified)
|
||||
// should be returned, along with an optional slice of ResourceIdentifiers specifying
|
||||
// additional related items that should be backed up now, an optional operationID for actions which
|
||||
// initiate asynchronous actions, and a second slice of ResourceIdentifiers specifying related items
|
||||
// which should be backed up after all asynchronous operations have completed. This last field will be
|
||||
// ignored if operationID is empty, and should not be filled in unless the resource must be updated in the
|
||||
// backup after async operations complete (i.e. some of the item's kubernetes metadata will be updated
|
||||
// during the asynch operation which will be required during restore)
|
||||
Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error)
|
||||
|
||||
// Progress
|
||||
Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error)
|
||||
// Cancel
|
||||
Cancel(operationID string, backup *api.Backup) error
|
||||
|
||||
Three new methods will be added to the RestoreItemAction interface, and the
|
||||
RestoreItemActionExecuteOutput struct will be modified:
|
||||
|
||||
// Name returns the name of this RIA. Plugins which implement this interface must defined Name,
|
||||
// but its content is unimportant, as it won't actually be called via RPC. Velero's plugin infrastructure
|
||||
// will implement this directly rather than delegating to the RPC plugin in order to return the name
|
||||
// that the plugin was registered under. The plugins must implement the method to complete the interface.
|
||||
Name() string
|
||||
// Execute allows the ItemAction to perform arbitrary logic with the item being restored,
|
||||
// including mutating the item itself prior to restore. The item (unmodified or modified)
|
||||
// should be returned, an optional OperationID, along with an optional slice of ResourceIdentifiers
|
||||
// specifying additional related items that should be restored, a warning (which will be
|
||||
// logged but will not prevent the item from being restored) or error (which will be logged
|
||||
// and will prevent the item from being restored) if applicable. If OperationID is specified
|
||||
// then velero will wait for this operation to complete before the restore is marked Completed.
|
||||
Execute(input *RestoreItemActionExecuteInput) (*RestoreItemActionExecuteOutput, error)
|
||||
|
||||
|
||||
// Progress
|
||||
Progress(operationID string, restore *api.Restore) (velero.OperationProgress, error)
|
||||
|
||||
// Cancel
|
||||
Cancel(operationID string, restore *api.Restore) error
|
||||
|
||||
// RestoreItemActionExecuteOutput contains the output variables for the ItemAction's Execution function.
|
||||
type RestoreItemActionExecuteOutput struct {
|
||||
// UpdatedItem is the item being restored mutated by ItemAction.
|
||||
UpdatedItem runtime.Unstructured
|
||||
|
||||
// AdditionalItems is a list of additional related items that should
|
||||
// be restored.
|
||||
AdditionalItems []ResourceIdentifier
|
||||
|
||||
// SkipRestore tells velero to stop executing further actions
|
||||
// on this item, and skip the restore step. When this field's
|
||||
// value is true, AdditionalItems will be ignored.
|
||||
SkipRestore bool
|
||||
|
||||
// OperationID is an identifier which indicates an ongoing asynchronous action which Velero will
|
||||
// continue to monitor after restoring this item. If left blank, then there is no ongoing operation
|
||||
OperationID string
|
||||
}
|
||||
|
||||
## Changes in Velero backup format
|
||||
|
||||
No changes to the existing format are introduced by this change. As part of the backup workflow changes, a
|
||||
`<backup-name>-itemoperations.json.gz` file will be added that contains the items and operation IDs
|
||||
(snapshotIDs) returned by VolumeSnapshotter and BackupItemAction plugins. Also, the creation of the
|
||||
`velero-backup.json` object will not occur until the backup moves to one of the terminal phases
|
||||
(_Completed_, _PartiallyFailed_, or _Failed_). Reconciliation should ignore backups that do not
|
||||
have a `velero-backup.json` object.
|
||||
|
||||
The Backup/RestoreItemAction plugin identifier as well as the ItemID and OperationID will be stored
|
||||
in the `<backup-name>-itemoperations.json.gz`. When checking for progress, this info will be used
|
||||
to select the appropriate Backup/RestoreItemAction plugin to query for progress. Here's an example
|
||||
of what a record for a datamover plugin might look like:
|
||||
```
|
||||
{
|
||||
"spec": {
|
||||
"backupName": "backup-1",
|
||||
"backupUID": "f8c72709-0f73-46e1-a071-116bc4a76b07",
|
||||
"backupItemAction": "velero.io/volumesnapshotcontent-backup",
|
||||
"resourceIdentifier": {
|
||||
"Group": "snapshot.storage.k8s.io",
|
||||
"Resource": "VolumeSnapshotContent",
|
||||
"Namespace": "my-app",
|
||||
"Name": "my-volume-vsc"
|
||||
},
|
||||
"operationID": "<DataMoverBackup objectReference>",
|
||||
"itemsToUpdate": [
|
||||
{
|
||||
"Group": "velero.io",
|
||||
"Resource": "VolumeSnapshotBackup",
|
||||
"Namespace": "my-app",
|
||||
"Name": "vsb-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"operationPhase": "Completed",
|
||||
"error": "",
|
||||
"nCompleted": 12345,
|
||||
"nTotal": 12345,
|
||||
"operationUnits": "byte",
|
||||
"description": "",
|
||||
"Created": "2022-12-14T12:00:00Z",
|
||||
"Started": "2022-12-14T12:01:00Z",
|
||||
"Updated": "2022-12-14T12:11:02Z"
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The cluster that is creating the backup will have the Backup resource present and will be able to
|
||||
manage the backup before the backup completes.
|
||||
|
||||
If the Backup resource is removed (e.g. Velero is uninstalled) before a backup completes and writes
|
||||
its `velero-backup.json` object, the other objects in the object store for the backup will be
|
||||
effectively orphaned. This can currently happen but the current window is much smaller.
|
||||
|
||||
### `<backup-name>-itemoperations.json.gz`
|
||||
The itemoperations file is similar to the existing `<backup-name>-itemsnapshots.json.gz` Each snapshot taken via
|
||||
BackupItemAction will have a JSON record in the file. Exact format TBD.
|
||||
|
||||
This file will be uploaded to object storage at the end of processing all of the items in the
|
||||
backup, before the phase moves away from `InProgress`.
|
||||
|
||||
## Changes to Velero restores
|
||||
|
||||
A `<restore-name>-itemoperations.json.gz` file will be added that contains the items and operation
|
||||
IDs returned by RestoreItemActions. The format will be the same as the
|
||||
`<backup-name>-itemoperations.json.gz` generated for backups.
|
||||
|
||||
This file will be uploaded to object storage at the end of processing all of the items in the
|
||||
restore, before the phase moves away from `InProgress`.
|
||||
|
||||
## CSI snapshots
|
||||
|
||||
For systems such as EBS, a snapshot is not available until the storage system has transferred the
|
||||
snapshot to stable storage. CSI snapshots expose the _readyToUse_ state that, in the case of EBS,
|
||||
indicates that the snapshot has been transferred to durable storage and is ready to be used. The
|
||||
CSI BackupItemAction.Progress method will poll that field and when completed, return completion.
|
||||
|
||||
## vSphere plug-in
|
||||
|
||||
The vSphere Plug-in for Velero uploads snapshots to S3 in the background. This is also a
|
||||
BackupItemAction plug-in, it will check the status of the Upload records for the snapshot and return
|
||||
progress.
|
||||
|
||||
## Backup workflow changes
|
||||
|
||||
The backup workflow remains the same until we get to the point where the `velero-backup.json` object
|
||||
is written. At this point, Velero will
|
||||
run across all of the VolumeSnapshotter/BackupItemAction operations and call the _Progress_ method
|
||||
on each of them.
|
||||
|
||||
If all backup item operations have finished (either successfully or failed), the backup will move to
|
||||
one of the finalize phases.
|
||||
|
||||
If any of the snapshots or backup items are still being processed, the phase of the backup will be
|
||||
set to the appropriate phase (_WaitingForPluginOperations_ or
|
||||
_WaitingForPluginOperationsPartiallyFailed_), and the async backup operations controller will
|
||||
reconcile periodically and call Progress on any unfinished operations. In the event of any of the
|
||||
progress checks return an error, the phase will move to _WaitingForPluginOperationsPartiallyFailed_.
|
||||
|
||||
Once all operations have completed, the backup will be moved to one of the finalize phases, and the
|
||||
backup finalizer controller will update the the `velero-backup.json`in the object store with any
|
||||
resources necessary after asynchronous operations are complete and the backup will move to the
|
||||
appropriate terminal phase.
|
||||
|
||||
|
||||
## Restore workflow changes
|
||||
|
||||
The restore workflow remains the same until velero would currently move the backup into one of the
|
||||
terminal states. At this point, Velero will run across all of the RestoreItemAction operations and
|
||||
call the _Progress_ method on each of them.
|
||||
|
||||
If all restore item operations have finished (either successfully or failed), the restore will be
|
||||
completed and the restore will move to the appropriate terminal phase and the restore will be
|
||||
complete.
|
||||
|
||||
If any of the restore items are still being processed, the phase of the restore will be set to the
|
||||
appropriate phase (_WaitingForPluginOperations_ or _WaitingForPluginOperationsPartiallyFailed_), and
|
||||
the async restore operations controller will reconcile periodically and call Progress on any
|
||||
unfinished operations. In the event of any of the progress checks return an error, the phase will
|
||||
move to _WaitingForPluginOperationsPartiallyFailed_. Once all of the operations have completed, the
|
||||
restore will be moved to the appropriate terminal phase.
|
||||
|
||||
## Restart workflow
|
||||
|
||||
On restart, the Velero server will scan all Backup/Restore resources. Any Backup/Restore resources
|
||||
which are in the _InProgress_ phase will be moved to the _Failed_ phase. Any Backup/Restore
|
||||
resources in the _WaitingForPluginOperations_ or _WaitingForPluginOperationsPartiallyFailed_ phase
|
||||
will be treated as if they have been requeued and progress checked and the backup/restore will be
|
||||
requeued or moved to a terminal phase as appropriate.
|
||||
|
||||
## Notes on already-merged code which may need updating
|
||||
|
||||
Since this design is modifying a previously-approved design, there is some preparation work based on
|
||||
the earlier upload progress monitoring design that may need modification as a result of these
|
||||
updates. Here is a list of some of these items:
|
||||
|
||||
1. Consts for the "Uploading" and "UploadingPartiallyFailed" phases have already been defined. These
|
||||
will need to be removed when the "WaitingForPluginOperations" and
|
||||
"WaitingForPluginOperationsPartiallyFailed" phases are defined.
|
||||
- https://github.com/vmware-tanzu/velero/pull/3805
|
||||
1. Remove the ItemSnapshotter plugin APIs (and related code) since the revised design will reuse
|
||||
VolumeSnapshotter and BackupItemAction plugins.
|
||||
- https://github.com/vmware-tanzu/velero/pull/4077
|
||||
- https://github.com/vmware-tanzu/velero/pull/4417
|
||||
1. UploadProgressFeatureFlag shouldn't be needed anymore. The current design won't really need a
|
||||
feature flag here -- the new features will be added to V2 of the VolumeSnapshotter,
|
||||
BackupItemAction, and RestoreItemAction plugins, and it will only be used if there are plugins which
|
||||
return operation IDs.
|
||||
- https://github.com/vmware-tanzu/velero/pull/4416
|
||||
1. Adds <backup-name>-itemsnapshots.gz file to backup (when provided) -- this is still part of the
|
||||
revised design, so it should stay.
|
||||
- https://github.com/vmware-tanzu/velero/pull/4429
|
||||
1. Upload Progress Monitoring and Item Snapshotter basic support: This PR is not yet merged, so
|
||||
nothing will need to be reverted. While the implementation here will be useful in informing the
|
||||
implementation of the new design, several things have changed in the design proposal since the PR
|
||||
was written.
|
||||
- https://github.com/vmware-tanzu/velero/pull/4467
|
||||
|
||||
# Implementation tasks
|
||||
|
||||
VolumeSnapshotter new plugin APIs
|
||||
BackupItemAction new plugin APIs
|
||||
RestoreItemAction new plugin APIs
|
||||
New backup phases
|
||||
New restore phases
|
||||
Defer uploading `velero-backup.json`
|
||||
AWS EBS plug-in Progress implementation
|
||||
Operation monitoring
|
||||
Implementation of `<backup-name>-itemoperations.json.gz` file
|
||||
Implementation of `<restore-name>-itemoperations.json.gz` file
|
||||
Restart logic
|
||||
Change in reconciliation logic to ignore backups/restores that have not completed
|
||||
CSI plug-in BackupItemAction Progress implementation
|
||||
vSphere plug-in BackupItemAction Progress implementation (vSphere plug-in team)
|
||||
|
||||
|
||||
# Open Questions
|
||||
|
||||
1. Do we need a Cancel operation for VolumeSnapshotter?
|
||||
- From feedback, I'm thinking we probably don't need it. The only real purpose of Cancel
|
||||
here is to tell the plugin that Velero won't be waiting anymore, so if there are any
|
||||
required custom cancellation actions, now would be a good time to perform them. For snapshot
|
||||
uploads that are already in proress, there's not really anything else to cancel.
|
||||
2. Should we actually write the backup *before* moving to the WaitingForPluginOperations or
|
||||
WaitingForPluginOperationsPartiallyFailed phase rather than waiting until all operations
|
||||
have completed? The operations in question won't affect what gets written to object storage
|
||||
for the backup, and since we've already written the list of operations we're waiting for to
|
||||
object storage, writing the backup now would make the process resilient to Velero restart if
|
||||
it happens during WaitingForPluginOperations or WaitingForPluginOperationsPartiallyFailed
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
# Handle backup of volumes by resources filters
|
||||
|
||||
## Abstract
|
||||
Currently, Velero doesn't have one flexible way to handle volumes.
|
||||
|
||||
If users want to skip the backup of volumes or only backup some volumes in different namespaces in batch, currently they need to use the opt-in and opt-out approach one by one, or use label-selector but if it has big different labels on each different related pod, which is cumbersome when they have lots of volumes to handle with. it would be convenient if Velero could provide policies to handle the backup of volumes just by `some specific volumes conditions`.
|
||||
|
||||
## Background
|
||||
As of Today, Velero has lots of filters to handle (backup or skip backup) resources including resources filters like `IncludedNamespaces, ExcludedNamespaces`, label selectors like `LabelSelector, OrLabelSelectors`, annotation like `backup.velero.io/must-include-additional-items` etc. But it's not enough flexible to handle volumes, we need one generic way to handle volumes.
|
||||
|
||||
## Goals
|
||||
- Introducing flexible policies to handle volumes, and do not patch any labels or annotations to the pods or volumes.
|
||||
|
||||
## Non-Goals
|
||||
- We only handle volumes for backup and do not support restore.
|
||||
- Currently, only handles volumes, and does not support other resources.
|
||||
- Only environment-unrelated and platform-independent general volumes attributes are supported, do not support volumes attributes related to a specific environment.
|
||||
|
||||
## Use-cases/Scenarios
|
||||
### Skip backup volumes by some attributes
|
||||
Users want to skip PV with the requirements:
|
||||
- option to skip all PV data
|
||||
- option to skip specified PV type (RBD, NFS)
|
||||
- option to skip specified PV size
|
||||
- option to skip specified storage-class
|
||||
|
||||
## High-Level Design
|
||||
First, Velero will provide the user with one YAML file template and all supported volume policies will be in.
|
||||
|
||||
Second, writing your own configuration file by imitating the YAML template, it could be partial volume policies from the template.
|
||||
|
||||
Third, create one configmap from your own configuration file, and the configmap should be in Velero install namespace.
|
||||
|
||||
Fourth, create a backup with the command `velero backup create --resource-policies-configmap $policiesConfigmap`, which will reference the current backup to your volume policies. At the same time, Velero will validate all volume policies user imported, the backup will fail if the volume policies are not supported or some items could not be parsed.
|
||||
|
||||
Fifth, the current backup CR will record the reference of volume policies configmap.
|
||||
|
||||
Sixth, Velero first filters volumes by other current supported filters, at last, it will apply the volume policies to the filtered volumes to get the final matched volume to handle.
|
||||
|
||||
## Detailed Design
|
||||
The volume resources policies should contain a list of policies which is the combination of conditions and related `action`, when target volumes meet the conditions, the related `action` will take effection.
|
||||
|
||||
Below is the API Design for the user configuration:
|
||||
|
||||
### API Design
|
||||
```go
|
||||
type VolumeActionType string
|
||||
|
||||
const Skip VolumeActionType = "skip"
|
||||
|
||||
// Action defined as one action for a specific way of backup
|
||||
type Action struct {
|
||||
// Type defined specific type of action, it could be 'file-system-backup', 'volume-snapshot', or 'skip' currently
|
||||
Type VolumeActionType `yaml:"type"`
|
||||
// Parameters defined map of parameters when executing a specific action
|
||||
// +optional
|
||||
// +nullable
|
||||
Parameters map[string]interface{} `yaml:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// VolumePolicy defined policy to conditions to match Volumes and related action to handle matched Volumes
|
||||
type VolumePolicy struct {
|
||||
// Conditions defined list of conditions to match Volumes
|
||||
Conditions map[string]interface{} `yaml:"conditions"`
|
||||
Action Action `yaml:"action"`
|
||||
}
|
||||
|
||||
// ResourcePolicies currently defined slice of volume policies to handle backup
|
||||
type ResourcePolicies struct {
|
||||
Version string `yaml:"version"`
|
||||
VolumePolicies []VolumePolicy `yaml:"volumePolicies"`
|
||||
// we may support other resource policies in the future, and they could be added separately
|
||||
// OtherResourcePolicies: []OtherResourcePolicy
|
||||
}
|
||||
```
|
||||
|
||||
The policies YAML config file would look like this:
|
||||
```yaml
|
||||
version: v1
|
||||
volumePolicies:
|
||||
# it's a list and if the input item matches the first policy, the latters will be ignored
|
||||
# each policy consists of a list of conditions and an action
|
||||
|
||||
# each key in the object is one condition, and one policy will apply to resources that meet ALL conditions
|
||||
- conditions:
|
||||
# capacity condition matches the volumes whose capacity falls into the range
|
||||
capacity: "0,100Gi"
|
||||
csi:
|
||||
driver: aws.ebs.csi.driver
|
||||
fsType: ext4
|
||||
storageClass:
|
||||
- gp2
|
||||
- ebs-sc
|
||||
action:
|
||||
type: volume-snapshot
|
||||
parameters:
|
||||
# optional parameters which are custom-defined parameters when doing an action
|
||||
volume-snapshot-timeout: "6h"
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
storageClass:
|
||||
- gp2
|
||||
- ebs-sc
|
||||
action:
|
||||
type: file-system-backup
|
||||
- conditions:
|
||||
nfs:
|
||||
server: 192.168.200.90
|
||||
action:
|
||||
# type of file-system-backup could be defined a second time
|
||||
type: file-system-backup
|
||||
- conditions:
|
||||
nfs: {}
|
||||
action:
|
||||
type: skip
|
||||
- conditions:
|
||||
csi:
|
||||
driver: aws.efs.csi.driver
|
||||
action:
|
||||
type: skip
|
||||
```
|
||||
|
||||
### Filter rules
|
||||
#### VolumePolicies
|
||||
The whole resource policies consist of groups of volume policies.
|
||||
|
||||
For one specific volume policy which is a combination of one action and serval conditions. which means one action and serval conditions are the smallest unit of volume policy.
|
||||
|
||||
Volume policies are a list and if the target volumes match the first policy, the latter will be ignored, which would reduce the complexity of matching volumes especially when there are multiple complex volumes policies.
|
||||
|
||||
#### Action
|
||||
`Action` defined one action for a specific way of backup:
|
||||
- if choosing `Kopia` or `Restic`, the action value would be `file-system-backup`.
|
||||
- if choosing volume snapshot, the action value would be `volume-snapshot`.
|
||||
- if choosing skip backup of volume, the action value would be `skip`, and it will skip backup of volume no matter is `file-system-backup` or `volume-snapshot`.
|
||||
|
||||
The policies could be extended for later other ways of backup, which means it may have some other `Action` value that will be assigned in the future.
|
||||
|
||||
Both `file-system-backup` `volume-snapshot`, and `skip` could be partially or fully configured in the YAML file. And configuration could take effect only for the related action.
|
||||
|
||||
#### Conditions
|
||||
The conditions are serials of volume attributes, the matched Volumes should meet all the volume attributes in one conditions configuration.
|
||||
|
||||
##### Supported conditions
|
||||
In Velero 1.11, we want to support the volume attributes listed below:
|
||||
- capacity: matching volumes have the capacity that falls within this `capacity` range.
|
||||
- storageClass: matching volumes those with specified `storageClass`, such as `gp2`, `ebs-sc` in eks.
|
||||
- matching volumes that used specified volume sources.
|
||||
##### Parameters
|
||||
Parameters are optional for one specific action. For example, it could be `csi-snapshot-timeout: 6h` for CSI snapshot.
|
||||
|
||||
#### Special rule definitions:
|
||||
- One single condition in `Conditions` with a specific key and empty value, which means the value matches any value. For example, if the `conditions.nfs` is `{}`, it means if `NFS` is used as `persistentVolumeSource` in Persistent Volume will be skipped no matter what the NFS server or NFS Path is.
|
||||
|
||||
- The size of each single filter value should limit to 256 bytes in case of an unfriendly long variable assignment.
|
||||
|
||||
- For capacity for PV or size for Volume, the value should include the lower value and upper value concatenated by commas. And it has several combinations below:
|
||||
- "0,5Gi" or "0Gi,5Gi" which means capacity or size matches from 0 to 5Gi, including value 0 and value 5Gi
|
||||
- ",5Gi" which is equal to "0,5Gi"
|
||||
- "5Gi," which means capacity or size matches larger than 5Gi, including value 5Gi
|
||||
- "5Gi" which is not supported and will be failed in validating configuration.
|
||||
|
||||
### Configmap Reference
|
||||
Currently, resources policies are defined in `BackupSpec` struct, it will be more and more bloated with adding more and more filters which makes the size of `Backup` CR bigger and bigger, so we want to store the resources policies in configmap, and `Backup` CRD reference to current configmap.
|
||||
|
||||
the `configmap` user created would be like this:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
data:
|
||||
policies.yaml:
|
||||
----
|
||||
version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
csi:
|
||||
driver: aws.ebs.csi.driver
|
||||
fsType: ext4
|
||||
storageClass:
|
||||
- gp2
|
||||
- ebs-sc
|
||||
action:
|
||||
type: volume-snapshot
|
||||
parameters:
|
||||
volume-snapshot-timeout: "6h"
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
creationTimestamp: "2023-01-16T14:08:12Z"
|
||||
name: backup01
|
||||
namespace: velero
|
||||
resourceVersion: "17891025"
|
||||
uid: b73e7f76-fc9e-4e72-8e2e-79db717fe9f1
|
||||
```
|
||||
|
||||
A new variable `resourcePolices` would be added into `BackupSpec`, it's value is assigned with the current resources policy configmap
|
||||
```yaml
|
||||
apiVersion: velero.io/v1
|
||||
kind: Backup
|
||||
metadata:
|
||||
name: backup-1
|
||||
spec:
|
||||
resourcePolices:
|
||||
refType: Configmap
|
||||
ref: backup01
|
||||
...
|
||||
```
|
||||
The configmap only stores those assigned values, not the whole resources policies.
|
||||
|
||||
The name of the configmap is `$BackupName`, and it's in Velero install namespace.
|
||||
|
||||
#### Resource policies configmap related
|
||||
The life cycle of resource policies configmap is managed by the user instead of Velero, which could make it more flexible and easy to maintain.
|
||||
- The resource policies configmap will remain in the cluster until the user deletes it.
|
||||
- Unlike backup, the resource policies configmap will not sync to the new cluster. So if the user wants to use one resource policies that do not sync to the new cluster, the backup will fail with resource policies not found.
|
||||
- One resource policies configmap could be used by multiple backups.
|
||||
- If the backup referenced resource policies configmap is been deleted, it won't affect the already existing backups, but if the user wants to reference the deleted configmap to create one new backup, it will fail with resource policies not found.
|
||||
|
||||
#### Versioning
|
||||
We want to introduce the version field in the YAML data to contain break changes. Therefore, we won't follow a semver paradigm, for example in v1.11 the data look like this:
|
||||
```yaml
|
||||
version: v1
|
||||
volumePolicies:
|
||||
....
|
||||
```
|
||||
Hypothetically, in v1.12 we add new fields like clusterResourcePolicies, the version will remain as v1 b/c this change is backward compatible:
|
||||
```yaml
|
||||
version: v1
|
||||
volumePolicies:
|
||||
....
|
||||
clusterResourcePolicies:
|
||||
....
|
||||
```
|
||||
Suppose in v1.13, we have to introduce a break change, at this time we will bump up the version:
|
||||
```yaml
|
||||
version: v2
|
||||
# This is just an example, we should try to avoid break change
|
||||
volume-policies:
|
||||
....
|
||||
```
|
||||
We only support one version in Velero, so it won't be recognized if backup using a former version of YAML data.
|
||||
|
||||
#### Multiple versions supporting
|
||||
To manage the effort for maintenance, we will only support one version of the data in Velero. Suppose that there is one break change for the YAML data in Velero v1.13, we should bump up the config version to v2, and v2 is only supported in v1.13. For the existing data with version: v1, it should migrate them when the Velero startup, this won't hurt the existing backup schedule CR as it only references the configmap. To make the migration easier, the configmap for such resource filter policies should be labeled manually before Velero startup like this, Velero will migrate the labeled configmap.
|
||||
|
||||
We only support migrating from the previous version to the current version in case of complexity in data format conversion, which users could regenerate configmap in the new YAML data version, and it is easier to do version control.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
# This label can be optional but if this is not set, the backup will fail after the breaking change and the user will need to update the data manually
|
||||
velero.io/resource-filter-policies: "true"
|
||||
name: example
|
||||
namespace: velero
|
||||
data:
|
||||
.....
|
||||
```
|
||||
### Display of resources policies
|
||||
As the resource policies configmap is referenced by backup CR, the policies in configmap are not so intuitive, so we need to integrate policies in configmap to the output of the command `velero backup describe`, and make it more readable.
|
||||
|
||||
## Compatibility
|
||||
Currently, we have these resources filters:
|
||||
- IncludedNamespaces
|
||||
- ExcludedNamespaces
|
||||
- IncludedResources
|
||||
- ExcludedResources
|
||||
- LabelSelector
|
||||
- OrLabelSelectors
|
||||
- IncludeClusterResources
|
||||
- UseVolumeSnapshots
|
||||
- velero.io/exclude-from-backup=true
|
||||
- backup.velero.io/backup-volumes-excludes
|
||||
- backup.velero.io/backup-volumes
|
||||
- backup.velero.io/must-include-additional-items
|
||||
|
||||
So it should be careful with the combination of volumes resources policies and the above resources filters.
|
||||
- When volume resource policies conflict with the above resource filters, we should respect the above resource filters. For example, if the user used the opt-out approach to `backup.velero.io/backup-volumes-excludes` annotation on the pod and also defined include volume in volumes resources filters configuration, we should respect the opt-out approach to skip backup of the volume.
|
||||
- If volume resource policies conflict with themselves, the first matched policy will be respect.
|
||||
|
||||
## Implementation
|
||||
This implementation should be included in Velero v1.11.0
|
||||
|
||||
Currently, in Velero v1.11.0 we only support `Action`
|
||||
`skip`, and support `file-system-backup` and `volume-snapshot` for the later version. And `Parameters` in `Action` is also not supported in v1.11.0, we will support in a later version.
|
||||
|
||||
In Velero 1.11, we supported Conditions and format listed below:
|
||||
- capacity
|
||||
```yaml
|
||||
capacity: "10Gi,100Gi" // match volume has the size between 10Gi and 100Gi
|
||||
```
|
||||
- storageClass
|
||||
```yaml
|
||||
storageClass: // match volume has the storage class gp2 or ebs-sc
|
||||
- gp2
|
||||
- ebs-sc
|
||||
```
|
||||
- volume sources (currently only support below format and attributes)
|
||||
1. Specify the volume source name, the name could be `nfs`, `rbd`, `iscsi`, `csi` etc.
|
||||
```yaml
|
||||
nfs : {} // match any volume has nfs volume source
|
||||
|
||||
csi : {} // match any volume has csi volume source
|
||||
```
|
||||
|
||||
2. Specify details for the related volume source (currently we only support csi driver filter and nfs server or path filter)
|
||||
```yaml
|
||||
csi: // match volume has nfs volume source and using `aws.efs.csi.driver`
|
||||
driver: aws.efs.csi.driver
|
||||
|
||||
nfs: // match volume has nfs volume source and using below server and path
|
||||
server: 192.168.200.90
|
||||
path: /mnt/nfs
|
||||
```
|
||||
The conditions also could be extended in later versions, such as we could further supporting filtering other volume source detail not only NFS and CSI.
|
||||
|
||||
## Alternatives Considered
|
||||
### Configmap VS CRD
|
||||
Here we support the user define the YAML config file and storing the resources policies into configmap, also we could define one resource's policies CRD and store policies imported from the user-defined config file in the related CR.
|
||||
|
||||
But CRD is more like one kind of resource with status, Kubernetes API Server handles the lifecycle of a CR and handles it in different statuses. Compared to CRD, Configmap is more focused to store data.
|
||||
|
||||
## Open Issues
|
||||
Should we support more than one version of filter policies configmap?
|
||||
130
design/Implemented/riav2-design.md
Normal file
130
design/Implemented/riav2-design.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Design for RestoreItemAction v2 API
|
||||
|
||||
## Abstract
|
||||
This design includes the changes to the RestoreItemAction (RIA) api design as required by the [Item Action Progress Monitoring](general-progress-monitoring.md) feature.
|
||||
It also includes changes as required by the [Wait For Additional Items](wait-for-additional-items.md) feature.
|
||||
The BIA v2 interface will have three new methods, and the RestoreItemActionExecuteOutput() struct in the return from Execute() will have three optional fields added.
|
||||
If there are any additional RIA API changes that are needed in the same Velero release cycle as this change, those can be added here as well.
|
||||
|
||||
## Background
|
||||
This API change is needed to facilitate long-running plugin actions that may not be complete when the Execute() method returns.
|
||||
It is an optional feature, so plugins which don't need this feature can simply return an empty operation ID and the new methods can be no-ops.
|
||||
This will allow long-running plugin actions to continue in the background while Velero moves on to the next plugin, the next item, etc.
|
||||
The other change allows Velero to wait until newly-restored AdditionalItems returned by a RIA plugin are ready before moving on to restoring the current item.
|
||||
|
||||
## Goals
|
||||
- Allow for RIA Execute() to optionally initiate a long-running operation and report on operation status.
|
||||
- Allow for RIA to allow Velero to call back into the plugin to wait until AdditionalItems are ready before continuing with restore.
|
||||
|
||||
## Non Goals
|
||||
- Allowing velero control over when the long-running operation begins.
|
||||
|
||||
|
||||
## High-Level Design
|
||||
As per the [Plugin Versioning](plugin-versioning.md) design, a new RIAv2 plugin `.proto` file will be created to define the GRPC interface.
|
||||
v2 go files will also be created in `plugin/clientmgmt/restoreitemaction` and `plugin/framework/restoreitemaction`, and a new PluginKind will be created.
|
||||
Changes to RestoreItemActionExecuteOutput will be made to the existing struct.
|
||||
Since the new fields are optional elements of the struct, the new enlarged struct will work with both v1 and v2 plugins.
|
||||
The velero Restore process will be modified to reference v2 plugins instead of v1 plugins.
|
||||
An adapter will be created so that any existing RIA v1 plugin can be executed as a v2 plugin when executing a restore.
|
||||
|
||||
## Detailed Design
|
||||
|
||||
### proto changes (compiled into golang by protoc)
|
||||
|
||||
The v2 RestoreItemAction.proto will be like the current v1 version with the following changes:
|
||||
RestoreItemActionExecuteOutput gets three new fields (defined in the current (v1) RestoreItemAction.proto file:
|
||||
```
|
||||
message RestoreItemActionExecuteResponse {
|
||||
bytes item = 1;
|
||||
repeated ResourceIdentifier additionalItems = 2;
|
||||
bool skipRestore = 3;
|
||||
string operationID = 4;
|
||||
bool waitForAdditionalItems = 5;
|
||||
google.protobuf.Duration additionalItemsReadyTimeout = 6;
|
||||
}
|
||||
|
||||
```
|
||||
The RestoreItemAction service gets three new rpc methods:
|
||||
```
|
||||
service RestoreItemAction {
|
||||
rpc AppliesTo(RestoreItemActionAppliesToRequest) returns (RestoreItemActionAppliesToResponse);
|
||||
rpc Execute(RestoreItemActionExecuteRequest) returns (RestoreItemActionExecuteResponse);
|
||||
rpc Progress(RestoreItemActionProgressRequest) returns (RestoreItemActionProgressResponse);
|
||||
rpc Cancel(RestoreItemActionCancelRequest) returns (google.protobuf.Empty);
|
||||
rpc AreAdditionalItemsReady(RestoreItemActionItemsReadyRequest) returns (RestoreItemActionItemsReadyResponse);
|
||||
}
|
||||
|
||||
```
|
||||
To support these new rpc methods, we define new request/response message types:
|
||||
```
|
||||
message RestoreItemActionProgressRequest {
|
||||
string plugin = 1;
|
||||
string operationID = 2;
|
||||
bytes restore = 3;
|
||||
}
|
||||
|
||||
message RestoreItemActionProgressResponse {
|
||||
generated.OperationProgress progress = 1;
|
||||
}
|
||||
|
||||
message RestoreItemActionCancelRequest {
|
||||
string plugin = 1;
|
||||
string operationID = 2;
|
||||
bytes restore = 3;
|
||||
}
|
||||
|
||||
message RestoreItemActionItemsReadyRequest {
|
||||
string plugin = 1;
|
||||
bytes restore = 2;
|
||||
repeated ResourceIdentifier additionalItems = 3;
|
||||
}
|
||||
message RestoreItemActionItemsReadyResponse {
|
||||
bool ready = 1;
|
||||
}
|
||||
|
||||
```
|
||||
One new shared message type will be needed, as defined in the v2 BackupItemAction design:
|
||||
```
|
||||
message OperationProgress {
|
||||
bool completed = 1;
|
||||
string err = 2;
|
||||
int64 completed = 3;
|
||||
int64 total = 4;
|
||||
string operationUnits = 5;
|
||||
string description = 6;
|
||||
google.protobuf.Timestamp started = 7;
|
||||
google.protobuf.Timestamp updated = 8;
|
||||
}
|
||||
```
|
||||
|
||||
In addition to the three new rpc methods added to the RestoreItemAction interface, there is also a new `Name()` method. This one is only actually used internally by Velero to get the name that the plugin was registered with, but it still must be defined in a plugin which implements RestoreItemActionV2 in order to implement the interface. It doesn't really matter what it returns, though, as this particular method is not delegated to the plugin via RPC calls. The new (and modified) interface methods for `RestoreItemAction` are as follows:
|
||||
```
|
||||
type BackupItemAction interface {
|
||||
...
|
||||
Name() string
|
||||
...
|
||||
Progress(operationID string, restore *api.Restore) (velero.OperationProgress, error)
|
||||
Cancel(operationID string, backup *api.Restore) error
|
||||
AreAdditionalItemsReady(AdditionalItems []velero.ResourceIdentifier, restore *api.Restore) (bool, error)
|
||||
...
|
||||
}
|
||||
type RestoreItemActionExecuteOutput struct {
|
||||
UpdatedItem runtime.Unstructured
|
||||
AdditionalItems []ResourceIdentifier
|
||||
SkipRestore bool
|
||||
OperationID string
|
||||
WaitForAdditionalItems bool
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
A new PluginKind, `RestoreItemActionV2`, will be created, and the restore process will be modified to use this plugin kind.
|
||||
See [Plugin Versioning](plugin-versioning.md) for more details on implementation plans, including v1 adapters, etc.
|
||||
|
||||
|
||||
## Compatibility
|
||||
The included v1 adapter will allow any existing RestoreItemAction plugin to work as expected, with no-op AreAdditionalItemsReady(), Progress(), and Cancel() methods.
|
||||
|
||||
## Implementation
|
||||
This will be implemented during the Velero 1.11 development cycle.
|
||||
84
design/vsv2-design.md
Normal file
84
design/vsv2-design.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Design for VolumeSnapshotter v2 API
|
||||
|
||||
## Abstract
|
||||
This design includes the changes to the VolumeSnapshotter api design as required by the [Item Action Progress Monitoring](general-progress-monitoring.md) feature.
|
||||
The VolumeSnapshotter v2 interface will have two new methods.
|
||||
If there are any additional VolumeSnapshotter API changes that are needed in the same Velero release cycle as this change, those can be added here as well.
|
||||
|
||||
## Background
|
||||
This API change is needed to facilitate long-running plugin actions that may not be complete when the Execute() method returns.
|
||||
The existing snapshotID returned by CreateSnapshot will be used as the operation ID.
|
||||
This will allow long-running plugin actions to continue in the background while Velero moves on to the next plugin, the next item, etc.
|
||||
|
||||
## Goals
|
||||
- Allow for VolumeSnapshotter CreateSnapshot() to initiate a long-running operation and report on operation status.
|
||||
|
||||
## Non Goals
|
||||
- Allowing velero control over when the long-running operation begins.
|
||||
|
||||
|
||||
## High-Level Design
|
||||
As per the [Plugin Versioning](plugin-versioning.md) design, a new VolumeSnapshotterv2 plugin `.proto` file will be created to define the GRPC interface.
|
||||
v2 go files will also be created in `plugin/clientmgmt/volumesnapshotter` and `plugin/framework/volumesnapshotter`, and a new PluginKind will be created.
|
||||
The velero Backup process will be modified to reference v2 plugins instead of v1 plugins.
|
||||
An adapter will be created so that any existing VolumeSnapshotter v1 plugin can be executed as a v2 plugin when executing a backup.
|
||||
|
||||
## Detailed Design
|
||||
|
||||
### proto changes (compiled into golang by protoc)
|
||||
|
||||
The v2 VolumeSnapshotter.proto will be like the current v1 version with the following changes:
|
||||
The VolumeSnapshotter service gets two new rpc methods:
|
||||
```
|
||||
service VolumeSnapshotter {
|
||||
rpc Init(VolumeSnapshotterInitRequest) returns (Empty);
|
||||
rpc CreateVolumeFromSnapshot(CreateVolumeRequest) returns (CreateVolumeResponse);
|
||||
rpc GetVolumeInfo(GetVolumeInfoRequest) returns (GetVolumeInfoResponse);
|
||||
rpc CreateSnapshot(CreateSnapshotRequest) returns (CreateSnapshotResponse);
|
||||
rpc DeleteSnapshot(DeleteSnapshotRequest) returns (Empty);
|
||||
rpc GetVolumeID(GetVolumeIDRequest) returns (GetVolumeIDResponse);
|
||||
rpc SetVolumeID(SetVolumeIDRequest) returns (SetVolumeIDResponse);
|
||||
rpc Progress(VolumeSnapshotterProgressRequest) returns (VolumeSnapshotterProgressResponse);
|
||||
rpc Cancel(VolumeSnapshotterCancelRequest) returns (google.protobuf.Empty);
|
||||
}
|
||||
```
|
||||
To support these new rpc methods, we define new request/response message types:
|
||||
```
|
||||
message VolumeSnapshotterProgressRequest {
|
||||
string plugin = 1;
|
||||
string snapshotID = 2;
|
||||
}
|
||||
|
||||
message VolumeSnapshotterProgressResponse {
|
||||
generated.OperationProgress progress = 1;
|
||||
}
|
||||
|
||||
message VolumeSnapshotterCancelRequest {
|
||||
string plugin = 1;
|
||||
string operationID = 2;
|
||||
}
|
||||
|
||||
```
|
||||
One new shared message type will be needed, as defined in the v2 BackupItemAction design:
|
||||
```
|
||||
message OperationProgress {
|
||||
bool completed = 1;
|
||||
string err = 2;
|
||||
int64 completed = 3;
|
||||
int64 total = 4;
|
||||
string operationUnits = 5;
|
||||
string description = 6;
|
||||
google.protobuf.Timestamp started = 7;
|
||||
google.protobuf.Timestamp updated = 8;
|
||||
}
|
||||
```
|
||||
|
||||
A new PluginKind, `VolumeSnapshotterV2`, will be created, and the backup process will be modified to use this plugin kind.
|
||||
See [Plugin Versioning](plugin-versioning.md) for more details on implementation plans, including v1 adapters, etc.
|
||||
|
||||
|
||||
## Compatibility
|
||||
The included v1 adapter will allow any existing VolumeSnapshotter plugin to work as expected, with no-op Progress() and Cancel() methods.
|
||||
|
||||
## Implementation
|
||||
This will be implemented during the Velero 1.11 development cycle.
|
||||
42
go.mod
42
go.mod
@@ -1,13 +1,13 @@
|
||||
module github.com/vmware-tanzu/velero
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.21.0
|
||||
github.com/Azure/azure-pipeline-go v0.2.3
|
||||
github.com/Azure/azure-sdk-for-go v61.4.0+incompatible
|
||||
github.com/Azure/azure-sdk-for-go v67.2.0+incompatible
|
||||
github.com/Azure/azure-storage-blob-go v0.14.0
|
||||
github.com/Azure/go-autorest/autorest v0.11.21
|
||||
github.com/Azure/go-autorest/autorest v0.11.27
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8
|
||||
github.com/Azure/go-autorest/autorest/to v0.3.0
|
||||
github.com/aws/aws-sdk-go v1.43.31
|
||||
@@ -24,7 +24,7 @@ require (
|
||||
github.com/kopia/kopia v0.10.7
|
||||
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.18.1
|
||||
github.com/onsi/gomega v1.20.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.12.2
|
||||
github.com/robfig/cron v1.1.0
|
||||
@@ -32,23 +32,24 @@ require (
|
||||
github.com/spf13/afero v1.6.0
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/vmware-tanzu/crash-diagnostics v0.3.7
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
|
||||
golang.org/x/net v0.1.1-0.20221104162952-702349b0e862
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
google.golang.org/api v0.74.0
|
||||
google.golang.org/grpc v1.45.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
k8s.io/api v0.24.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.25.6
|
||||
k8s.io/apiextensions-apiserver v0.24.2
|
||||
k8s.io/apimachinery v0.24.2
|
||||
k8s.io/apimachinery v0.25.6
|
||||
k8s.io/cli-runtime v0.24.0
|
||||
k8s.io/client-go v0.24.2
|
||||
k8s.io/klog/v2 v2.60.1
|
||||
k8s.io/client-go v0.25.6
|
||||
k8s.io/klog/v2 v2.70.1
|
||||
k8s.io/kube-aggregator v0.19.12
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
|
||||
sigs.k8s.io/controller-runtime v0.12.2
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
@@ -61,7 +62,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.16 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.2.0 // indirect
|
||||
@@ -122,7 +123,7 @@ require (
|
||||
github.com/prometheus/common v0.34.0 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rs/xid v1.3.0 // indirect
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/stretchr/objx v0.4.0 // indirect
|
||||
github.com/vladimirvivien/gexe v0.1.1 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
@@ -132,9 +133,9 @@ require (
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
|
||||
golang.org/x/exp v0.0.0-20210916165020-5cb4fee858ee // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/term v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/term v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
@@ -144,11 +145,8 @@ require (
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/component-base v0.24.2 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220614142933-1062c7ade5f8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
)
|
||||
|
||||
replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2
|
||||
|
||||
85
go.sum
85
go.sum
@@ -60,8 +60,8 @@ cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKu
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
|
||||
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||
github.com/Azure/azure-sdk-for-go v61.4.0+incompatible h1:BF2Pm3aQWIa6q9KmxyF1JYKYXtVw67vtvu2Wd54NGuY=
|
||||
github.com/Azure/azure-sdk-for-go v61.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v67.2.0+incompatible h1:Uu/Ww6ernvPTrpq31kITVTIm/I5jlJ1wjtEH/bmSB2k=
|
||||
github.com/Azure/azure-sdk-for-go v67.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1 h1:qoVeMsc9/fh/yhxVaA0obYjVH/oI/ihrOoMwsLS9KSA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3 h1:E+m3SkZCN0Bf5q7YdTs5lSm2CYY3CK4spn5OmUIiQtk=
|
||||
@@ -78,16 +78,16 @@ github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+B
|
||||
github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
|
||||
github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
|
||||
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
|
||||
github.com/Azure/go-autorest/autorest v0.11.21 h1:w77zY/9RnUAWcIQyDC0Fc89mCvwftR8F+zsR/OH6enk=
|
||||
github.com/Azure/go-autorest/autorest v0.11.21/go.mod h1:Do/yuMSW/13ayUkcVREpsMHGG+MvV81uzSCFgYPj4tM=
|
||||
github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A=
|
||||
github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.16 h1:P8An8Z9rH1ldbOLdFpxYorgOt2sywL9V24dAwWHPuGc=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.16/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY=
|
||||
@@ -99,8 +99,9 @@ github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSY
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
|
||||
github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8=
|
||||
github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4=
|
||||
@@ -293,9 +294,13 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -379,7 +384,6 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
@@ -475,6 +479,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A=
|
||||
@@ -587,18 +593,15 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
||||
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
@@ -695,16 +698,18 @@ github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
@@ -802,6 +807,7 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
@@ -893,7 +899,6 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@@ -905,8 +910,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.1.1-0.20221104162952-702349b0e862 h1:KrLJ+iz8J6j6VVr/OCfULAcK+xozUmWE43fKpMR4MlI=
|
||||
golang.org/x/net v0.1.1-0.20221104162952-702349b0e862/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
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=
|
||||
@@ -1028,14 +1033,14 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1045,8 +1050,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1057,8 +1062,10 @@ golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
@@ -1352,16 +1359,18 @@ k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw=
|
||||
k8s.io/api v0.19.12/go.mod h1:EK+KvSq2urA6+CjVdZyAHEphXoLq2K2eW6lxOzTKSaY=
|
||||
k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8=
|
||||
k8s.io/api v0.24.0/go.mod h1:5Jl90IUrJHUJYEMANRURMiVvJ0g7Ax7r3R1bqO8zx8I=
|
||||
k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI=
|
||||
k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg=
|
||||
k8s.io/api v0.25.6 h1:LwDY2H6kD/3R8TekJYYaJWOdekNdXDO44eVpX6sNtJA=
|
||||
k8s.io/api v0.25.6/go.mod h1:bVp01KUcl8VUHFBTJMOknWNo7XvR0cMbeTTuFg1zCUs=
|
||||
k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k=
|
||||
k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ=
|
||||
k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
|
||||
k8s.io/apimachinery v0.19.12/go.mod h1:9eb44nUQSsz9QZiilFRuMj3ZbTmoWolU8S2gnXoRMjo=
|
||||
k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
|
||||
k8s.io/apimachinery v0.24.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
|
||||
k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM=
|
||||
k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
|
||||
k8s.io/apimachinery v0.25.6 h1:r6KIF2AHwLqFfZ0LcOA3I11SF62YZK83dxj1fn14NOQ=
|
||||
k8s.io/apimachinery v0.25.6/go.mod h1:1S2i1QHkmxc8+EZCIxe/fX5hpldVXk4gvnJInMEb8D4=
|
||||
k8s.io/apiserver v0.19.12/go.mod h1:ldZAZTNIKfMMv/UUEhk6UyTXC0/34iRdNFHo+MJOPc4=
|
||||
k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI=
|
||||
k8s.io/cli-runtime v0.22.2/go.mod h1:tkm2YeORFpbgQHEK/igqttvPTRIHFRz5kATlw53zlMI=
|
||||
@@ -1371,8 +1380,9 @@ k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU=
|
||||
k8s.io/client-go v0.19.12/go.mod h1:BAGKQraZ6fDmXhT46pGXWZQQqN7P4E0BJux0+9O6Gt0=
|
||||
k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U=
|
||||
k8s.io/client-go v0.24.0/go.mod h1:VFPQET+cAFpYxh6Bq6f4xyMY80G6jKKktU6G0m00VDw=
|
||||
k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA=
|
||||
k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30=
|
||||
k8s.io/client-go v0.25.6 h1:CHxACHi0DijmlYyUR7ooZoXnD5P8jYLgBHcxp775x/U=
|
||||
k8s.io/client-go v0.25.6/go.mod h1:s9mMAGFYiH3Z66j7BESzu0GEradT9GQ2LjFf/YRrnyc=
|
||||
k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk=
|
||||
k8s.io/code-generator v0.19.12/go.mod h1:ADrDvaUQWGn4a8lX0ONtzb7uFmDRQOMSYIMk1qWIAx8=
|
||||
k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w=
|
||||
@@ -1386,20 +1396,22 @@ k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAE
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
||||
k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc=
|
||||
k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ=
|
||||
k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-aggregator v0.19.12 h1:OwyNUe/7/gxzEnaLd3sC9Yrpx0fZAERzvFslX5Qq5g8=
|
||||
k8s.io/kube-aggregator v0.19.12/go.mod h1:K76wPd03pSHEmS1FgJOcpryac5C3va4cbCvSu+4EmE0=
|
||||
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
|
||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
|
||||
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk=
|
||||
k8s.io/kube-openapi v0.0.0-20220614142933-1062c7ade5f8 h1:IyQ1DifCBk589JD4Cm2CT2poIdO3lfPzz3WwVh1Ugf8=
|
||||
k8s.io/kube-openapi v0.0.0-20220614142933-1062c7ade5f8/go.mod h1:guXtiQW/y/AWAfPSOaI/1eY0TGBAmL5OygiIyUOKDRc=
|
||||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA=
|
||||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU=
|
||||
k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc=
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
@@ -1408,8 +1420,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lR
|
||||
sigs.k8s.io/controller-runtime v0.12.2 h1:nqV02cvhbAj7tbt21bpPpTByrXGn2INHRsi39lXy9sE=
|
||||
sigs.k8s.io/controller-runtime v0.12.2/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0=
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
|
||||
sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 h1:2sgAQQcY0dEW2SsQwTXhQV4vO6+rSslYx8K3XmM5hqQ=
|
||||
sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g=
|
||||
sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI=
|
||||
sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM=
|
||||
@@ -1418,8 +1430,9 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
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.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
|
||||
@@ -7,18 +7,11 @@ run:
|
||||
concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 5m
|
||||
timeout: 20m
|
||||
|
||||
# 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
|
||||
|
||||
# list of build tags, all linters use it. Default is empty list.
|
||||
#build-tags:
|
||||
# - mytag
|
||||
|
||||
# which dirs to skip: issues from them won't be reported;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but default dirs are skipped independently
|
||||
@@ -294,77 +287,23 @@ linters-settings:
|
||||
# Allow leading comments to be separated with empty liens
|
||||
allow-separated-leading-comment: false
|
||||
|
||||
# The custom section can be used to define linter plugins to be loaded at runtime. See README doc
|
||||
# for more info.
|
||||
# custom:
|
||||
# Each custom linter should have a unique name.
|
||||
# example:
|
||||
# The path to the plugin *.so. Can be absolute or local. Required for each custom linter
|
||||
# path: /path/to/example.so
|
||||
# The description of the linter. Optional, just for documentation purposes.
|
||||
# description: This is an example usage of a plugin linter.
|
||||
# Intended to point to the repo location of the linter. Optional, just for documentation purposes.
|
||||
# original-url: github.com/golangci/example-linter
|
||||
|
||||
linters:
|
||||
# enable:
|
||||
# - megacheck
|
||||
# - govet
|
||||
# disable:
|
||||
# - maligned
|
||||
# - prealloc
|
||||
disable-all: true
|
||||
presets:
|
||||
# - bugs
|
||||
# - unused
|
||||
enable:
|
||||
- bodyclose
|
||||
- goconst
|
||||
- gofmt
|
||||
- goheader
|
||||
- goimports
|
||||
- gosec
|
||||
- misspell
|
||||
- typecheck
|
||||
- unparam
|
||||
- unused
|
||||
fast: 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:
|
||||
# - abcdef
|
||||
#
|
||||
# # Excluding configuration per-path, per-linter, per-text and per-source
|
||||
# exclude-rules:
|
||||
# # Exclude some linters from running on tests files.
|
||||
# - path: _test\.go
|
||||
# linters:
|
||||
# - gocyclo
|
||||
# - errcheck
|
||||
# - dupl
|
||||
# - gosec
|
||||
#
|
||||
# # Exclude known linters from partially hard-vendored code,
|
||||
# # which is impossible to exclude via "nolint" comments.
|
||||
# - path: internal/hmac/
|
||||
# text: "weak cryptographic primitive"
|
||||
# linters:
|
||||
# - gosec
|
||||
#
|
||||
# # Exclude some staticcheck messages
|
||||
# - linters:
|
||||
# - staticcheck
|
||||
# text: "SA9003:"
|
||||
#
|
||||
# # Exclude lll issues for long lines with go:generate
|
||||
# - linters:
|
||||
# - lll
|
||||
# source: "^//go:generate "
|
||||
|
||||
# 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: true
|
||||
|
||||
# The default value is false. If set to true exclude and exclude-rules
|
||||
# regular expressions become case sensitive.
|
||||
exclude-case-sensitive: false
|
||||
|
||||
# The list of ids of default excludes to include or disable. By default it's empty.
|
||||
include:
|
||||
- EXC0002 # disable excluding of issues about comments from golint
|
||||
@@ -375,19 +314,8 @@ issues:
|
||||
# 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.
|
||||
new: false
|
||||
|
||||
# Show only new issues created after git revision `REV`
|
||||
new-from-rev: REV
|
||||
|
||||
# Show only new issues created in git patch with set file path.
|
||||
new-from-patch: path/to/patch/file
|
||||
# new-from-rev: origin/main
|
||||
|
||||
severity:
|
||||
# Default value is empty string.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM --platform=linux/amd64 golang:1.18.10-bullseye
|
||||
FROM --platform=linux/amd64 golang:1.20.6-bullseye
|
||||
|
||||
ARG GOPROXY
|
||||
|
||||
@@ -20,12 +20,6 @@ ENV GO111MODULE=on
|
||||
# Use a proxy for go modules to reduce the likelihood of various hosts being down and breaking the build
|
||||
ENV GOPROXY=${GOPROXY}
|
||||
|
||||
# get code-generation tools (for now keep in GOPATH since they're not fully modules-compatible yet)
|
||||
RUN mkdir -p /go/src/k8s.io
|
||||
WORKDIR /go/src/k8s.io
|
||||
RUN git config --global advice.detachedHead false
|
||||
RUN git clone -b v0.22.2 https://github.com/kubernetes/code-generator
|
||||
|
||||
# kubebuilder test bundle is separated from kubebuilder. Need to setup it for CI test.
|
||||
RUN curl -sSLo envtest-bins.tar.gz https://go.kubebuilder.io/test-tools/1.22.1/linux/amd64 && \
|
||||
mkdir /usr/local/kubebuilder && \
|
||||
@@ -48,17 +42,21 @@ RUN apt-get update && apt-get install -y unzip
|
||||
RUN wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-x86_64.zip && \
|
||||
unzip protoc-3.14.0-linux-x86_64.zip && \
|
||||
mv bin/protoc /usr/bin/protoc && \
|
||||
mv include/google /usr/include && \
|
||||
chmod a+x /usr/include/google && \
|
||||
chmod a+x /usr/include/google/protobuf && \
|
||||
chmod a+r -R /usr/include/google && \
|
||||
chmod +x /usr/bin/protoc
|
||||
RUN go install github.com/golang/protobuf/protoc-gen-go@v1.4.3
|
||||
|
||||
# get goreleaser
|
||||
RUN wget --quiet https://github.com/goreleaser/goreleaser/releases/download/v0.120.8/goreleaser_Linux_x86_64.tar.gz && \
|
||||
RUN wget --quiet https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_x86_64.tar.gz && \
|
||||
tar xvf goreleaser_Linux_x86_64.tar.gz && \
|
||||
mv goreleaser /usr/bin/goreleaser && \
|
||||
chmod +x /usr/bin/goreleaser
|
||||
|
||||
# get golangci-lint
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.0
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.51.0
|
||||
|
||||
# install kubectl
|
||||
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
|
||||
|
||||
@@ -50,7 +50,6 @@ fi
|
||||
mkdir ${build_path}/restic
|
||||
git clone -b v${RESTIC_VERSION} https://github.com/restic/restic.git ${build_path}/restic
|
||||
pushd ${build_path}/restic
|
||||
git apply /go/src/github.com/vmware-tanzu/velero/hack/modify_acces_denied_code.txt
|
||||
git apply /go/src/github.com/vmware-tanzu/velero/hack/fix_restic_cve.txt
|
||||
go run build.go --goos "${GOOS}" --goarch "${GOARCH}" --goarm "${GOARM}" -o ${restic_bin}
|
||||
chmod +x ${restic_bin}
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
@@ -41,7 +40,7 @@ package crds
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
|
||||
apiextinstall "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
|
||||
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
@@ -65,7 +64,7 @@ func crds() []*apiextv1.CustomResourceDefinition {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(gzr)
|
||||
bytes, err := io.ReadAll(gzr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -87,7 +86,7 @@ type templateData struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
headerBytes, err := ioutil.ReadFile(goHeaderFile)
|
||||
headerBytes, err := os.ReadFile(goHeaderFile)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
@@ -97,7 +96,7 @@ func main() {
|
||||
}
|
||||
|
||||
// This is relative to config/crd/crds
|
||||
manifests, err := ioutil.ReadDir("../bases")
|
||||
manifests, err := os.ReadDir("../bases")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
@@ -1,97 +1,60 @@
|
||||
diff --git a/go.mod b/go.mod
|
||||
index d819a6be7..0629ed935 100644
|
||||
index 5f939c481..6f281b45d 100644
|
||||
--- a/go.mod
|
||||
+++ b/go.mod
|
||||
@@ -35,12 +35,12 @@ require (
|
||||
github.com/spf13/cobra v1.5.0
|
||||
@@ -25,12 +25,12 @@ require (
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
|
||||
- golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c
|
||||
+ golang.org/x/net v0.1.1-0.20221104162952-702349b0e862
|
||||
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
||||
- golang.org/x/sys v0.0.0-20220818161305-2296e01440c6
|
||||
- golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
|
||||
- golang.org/x/text v0.3.7
|
||||
+ golang.org/x/sys v0.1.0
|
||||
+ golang.org/x/term v0.1.0
|
||||
+ golang.org/x/text v0.4.0
|
||||
google.golang.org/api v0.93.0
|
||||
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
golang.org/x/crypto v0.5.0
|
||||
- golang.org/x/net v0.5.0
|
||||
+ golang.org/x/net v0.7.0
|
||||
golang.org/x/oauth2 v0.4.0
|
||||
golang.org/x/sync v0.1.0
|
||||
- golang.org/x/sys v0.4.0
|
||||
- golang.org/x/term v0.4.0
|
||||
- golang.org/x/text v0.6.0
|
||||
+ golang.org/x/sys v0.5.0
|
||||
+ golang.org/x/term v0.5.0
|
||||
+ golang.org/x/text v0.7.0
|
||||
google.golang.org/api v0.106.0
|
||||
)
|
||||
|
||||
diff --git a/go.sum b/go.sum
|
||||
index 959651048..8dea7af8a 100644
|
||||
index 026e1d2fa..da35b7a6c 100644
|
||||
--- a/go.sum
|
||||
+++ b/go.sum
|
||||
@@ -319,6 +319,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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=
|
||||
@@ -373,6 +374,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
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=
|
||||
@@ -418,8 +420,8 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
-golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c h1:JVAXQ10yGGVbSyoer5VILysz6YKjdNT2bsvlayjqhes=
|
||||
-golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
+golang.org/x/net v0.1.1-0.20221104162952-702349b0e862 h1:KrLJ+iz8J6j6VVr/OCfULAcK+xozUmWE43fKpMR4MlI=
|
||||
+golang.org/x/net v0.1.1-0.20221104162952-702349b0e862/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
@@ -189,8 +189,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
-golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
-golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
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=
|
||||
@@ -454,6 +456,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -522,13 +525,12 @@ golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
||||
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
||||
@@ -214,17 +214,17 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
-golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U=
|
||||
-golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
+golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
-golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
-golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
|
||||
-golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
+golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
|
||||
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
-golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
||||
-golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
+golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
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=
|
||||
@@ -537,8 +539,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
+golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -593,6 +596,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
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/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
-golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
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=
|
||||
|
||||
11
hack/lint.sh
11
hack/lint.sh
@@ -14,23 +14,14 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
LINTERS=${1:-"gosec,goconst,gofmt,goimports,unparam"}
|
||||
ALL=${2:-false}
|
||||
|
||||
HACK_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
|
||||
# Printing out cache status
|
||||
golangci-lint cache status
|
||||
|
||||
if [[ $ALL == true ]] ; then
|
||||
action=""
|
||||
else
|
||||
action="-n"
|
||||
fi
|
||||
|
||||
# Enable GL_DEBUG line below for debug messages for golangci-lint
|
||||
# export GL_DEBUG=loader,gocritic,env
|
||||
CMD="golangci-lint run -E ${LINTERS} $action -c $HACK_DIR/../golangci.yaml"
|
||||
CMD="golangci-lint run -c $HACK_DIR/../golangci.yaml"
|
||||
echo "Running $CMD"
|
||||
|
||||
eval $CMD
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
diff --git a/internal/backend/s3/s3.go b/internal/backend/s3/s3.go
|
||||
index 0b3816c06..eec10f9c7 100644
|
||||
--- a/internal/backend/s3/s3.go
|
||||
+++ b/internal/backend/s3/s3.go
|
||||
@@ -164,7 +164,7 @@ func isAccessDenied(err error) bool {
|
||||
debug.Log("isAccessDenied(%T, %#v)", err, err)
|
||||
|
||||
var e minio.ErrorResponse
|
||||
- return errors.As(err, &e) && e.Code == "Access Denied"
|
||||
+ return errors.As(err, &e) && e.Code == "AccessDenied"
|
||||
}
|
||||
|
||||
// IsNotExist returns true if the error is caused by a not existing file.
|
||||
@@ -24,11 +24,12 @@ import (
|
||||
|
||||
// This regex should match both our GA format (example: v1.4.3) and pre-release formats (v1.2.4-beta.2, v1.5.0-rc.1)
|
||||
// The following sub-capture groups are defined:
|
||||
//
|
||||
// major
|
||||
// minor
|
||||
// patch
|
||||
// prerelease (this will be alpha/beta/rc followed by a ".", followed by 1 or more digits (alpha.5)
|
||||
var release_regex *regexp.Regexp = regexp.MustCompile("^v(?P<major>[[:digit:]]+)\\.(?P<minor>[[:digit:]]+)\\.(?P<patch>[[:digit:]]+)(-{1}(?P<prerelease>(alpha|beta|rc)\\.[[:digit:]]+))*")
|
||||
var release_regex *regexp.Regexp = regexp.MustCompile(`^v(?P<major>[[:digit:]]+)\.(?P<minor>[[:digit:]]+)\.(?P<patch>[[:digit:]]+)(-{1}(?P<prerelease>(alpha|beta|rc)\.[[:digit:]]+))*`)
|
||||
|
||||
// This small program exists because checking the VELERO_VERSION rules in bash is difficult, and difficult to test for correctness.
|
||||
// Calling it with --verify will verify whether or not the VELERO_VERSION environment variable is a valid version string, without parsing for its components.
|
||||
|
||||
@@ -46,12 +46,12 @@ fi
|
||||
if [[ "${PUBLISH:-}" != "TRUE" ]]; then
|
||||
echo "Not set to publish"
|
||||
goreleaser release \
|
||||
--rm-dist \
|
||||
--clean \
|
||||
--release-notes="${RELEASE_NOTES_FILE}" \
|
||||
--skip-publish
|
||||
else
|
||||
echo "Getting ready to publish"
|
||||
goreleaser release \
|
||||
--rm-dist \
|
||||
--clean \
|
||||
--release-notes="${RELEASE_NOTES_FILE}"
|
||||
fi
|
||||
|
||||
@@ -25,7 +25,6 @@ TARGETS=(
|
||||
./cmd/...
|
||||
./pkg/...
|
||||
./internal/...
|
||||
./test/...
|
||||
)
|
||||
|
||||
if [[ ${#@} -ne 0 ]]; then
|
||||
|
||||
@@ -19,6 +19,6 @@ HACK_DIR=$(dirname "${BASH_SOURCE}")
|
||||
echo "Updating plugin proto"
|
||||
|
||||
echo protoc --version
|
||||
protoc pkg/plugin/proto/*.proto --go_out=plugins=grpc:pkg/plugin/generated/ --go_opt=module=github.com/vmware-tanzu/velero/pkg/plugin/generated -I pkg/plugin/proto/
|
||||
protoc pkg/plugin/proto/*.proto pkg/plugin/proto/*/*/*.proto --go_out=plugins=grpc:pkg/plugin/generated/ --go_opt=module=github.com/vmware-tanzu/velero/pkg/plugin/generated -I pkg/plugin/proto/ -I /usr/include
|
||||
|
||||
echo "Updating plugin proto - done!"
|
||||
|
||||
@@ -25,16 +25,17 @@ if [[ -z "${GOPATH}" ]]; then
|
||||
GOPATH=~/go
|
||||
fi
|
||||
|
||||
if [[ ! -d "${GOPATH}/src/k8s.io/code-generator" ]]; then
|
||||
echo "k8s.io/code-generator missing from GOPATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v controller-gen > /dev/null; then
|
||||
echo "controller-gen is missing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# get code-generation tools (for now keep in GOPATH since they're not fully modules-compatible yet)
|
||||
mkdir -p ${GOPATH}/src/k8s.io
|
||||
pushd ${GOPATH}/src/k8s.io
|
||||
git clone -b v0.22.2 https://github.com/kubernetes/code-generator
|
||||
popd
|
||||
|
||||
${GOPATH}/src/k8s.io/code-generator/generate-groups.sh \
|
||||
all \
|
||||
github.com/vmware-tanzu/velero/pkg/generated \
|
||||
|
||||
@@ -273,11 +273,25 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
||||
return nil
|
||||
}
|
||||
|
||||
// NoOpItemHookHandler is the an itemHookHandler for the Finalize controller where hooks don't run
|
||||
type NoOpItemHookHandler struct{}
|
||||
|
||||
func (h *NoOpItemHookHandler) HandleHooks(
|
||||
log logrus.FieldLogger,
|
||||
groupResource schema.GroupResource,
|
||||
obj runtime.Unstructured,
|
||||
resourceHooks []ResourceHook,
|
||||
phase hookPhase,
|
||||
) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func phasedKey(phase hookPhase, key string) string {
|
||||
if phase != "" {
|
||||
return fmt.Sprintf("%v.%v", phase, key)
|
||||
}
|
||||
return string(key)
|
||||
return key
|
||||
}
|
||||
|
||||
func getHookAnnotation(annotations map[string]string, key string, phase hookPhase) string {
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -40,15 +39,6 @@ import (
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
)
|
||||
|
||||
type mockItemHookHandler struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (h *mockItemHookHandler) HandleHooks(log logrus.FieldLogger, groupResource schema.GroupResource, obj runtime.Unstructured, resourceHooks []ResourceHook, phase hookPhase) error {
|
||||
args := h.Called(log, groupResource, obj, resourceHooks, phase)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestHandleHooksSkips(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -1342,8 +1332,8 @@ func TestGetRestoreHooksFromSpec(t *testing.T) {
|
||||
{
|
||||
Name: "h1",
|
||||
Selector: ResourceHookSelector{
|
||||
Namespaces: collections.NewIncludesExcludes().Includes([]string{"ns1", "ns2", "ns3"}...).Excludes([]string{"ns4", "ns5", "ns6"}...),
|
||||
Resources: collections.NewIncludesExcludes().Includes([]string{kuberesource.Pods.Resource}...),
|
||||
Namespaces: collections.NewIncludesExcludes().Includes("ns1", "ns2", "ns3").Excludes("ns4", "ns5", "ns6"),
|
||||
Resources: collections.NewIncludesExcludes().Includes(kuberesource.Pods.Resource),
|
||||
},
|
||||
RestoreHooks: []velerov1api.RestoreResourceHook{
|
||||
{
|
||||
|
||||
@@ -119,7 +119,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
)
|
||||
|
||||
if newPod.Status.Phase == v1.PodSucceeded || newPod.Status.Phase == v1.PodFailed {
|
||||
err := fmt.Errorf("Pod entered phase %s before some post-restore exec hooks ran", newPod.Status.Phase)
|
||||
err := fmt.Errorf("pod entered phase %s before some post-restore exec hooks ran", newPod.Status.Phase)
|
||||
podLog.Warning(err)
|
||||
cancel()
|
||||
return
|
||||
@@ -155,7 +155,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
)
|
||||
// Check the individual hook's wait timeout is not expired
|
||||
if hook.Hook.WaitTimeout.Duration != 0 && time.Since(waitStart) > hook.Hook.WaitTimeout.Duration {
|
||||
err := fmt.Errorf("Hook %s in container %s expired before executing", hook.HookName, hook.Hook.Container)
|
||||
err := fmt.Errorf("hook %s in container %s expired before executing", hook.HookName, hook.Hook.Container)
|
||||
hookLog.Error(err)
|
||||
if hook.Hook.OnError == velerov1api.HookErrorModeFail {
|
||||
errors = append(errors, err)
|
||||
@@ -194,7 +194,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
handler(newObj)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
err := fmt.Errorf("Pod %s deleted before all hooks were executed", kube.NamespaceAndName(pod))
|
||||
err := fmt.Errorf("pod %s deleted before all hooks were executed", kube.NamespaceAndName(pod))
|
||||
log.Error(err)
|
||||
cancel()
|
||||
},
|
||||
@@ -212,7 +212,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
if hook.executed {
|
||||
continue
|
||||
}
|
||||
err := fmt.Errorf("Hook %s in container %s in pod %s not executed: %v", hook.HookName, hook.Hook.Container, kube.NamespaceAndName(pod), ctx.Err())
|
||||
err := fmt.Errorf("hook %s in container %s in pod %s not executed: %v", hook.HookName, hook.Hook.Container, kube.NamespaceAndName(pod), ctx.Err())
|
||||
hookLog := log.WithFields(
|
||||
logrus.Fields{
|
||||
"hookSource": hook.HookSource,
|
||||
|
||||
@@ -465,7 +465,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
||||
},
|
||||
}).
|
||||
Result(),
|
||||
expectedErrors: []error{errors.New("Hook my-hook-1 in container container1 in pod default/my-pod not executed: context deadline exceeded")},
|
||||
expectedErrors: []error{errors.New("hook my-hook-1 in container container1 in pod default/my-pod not executed: context deadline exceeded")},
|
||||
byContainer: map[string][]PodExecRestoreHook{
|
||||
"container1": {
|
||||
{
|
||||
@@ -496,7 +496,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
||||
},
|
||||
}).
|
||||
Result(),
|
||||
expectedErrors: []error{errors.New("Hook my-hook-1 in container container1 in pod default/my-pod not executed: context deadline exceeded")},
|
||||
expectedErrors: []error{errors.New("hook my-hook-1 in container container1 in pod default/my-pod not executed: context deadline exceeded")},
|
||||
byContainer: map[string][]PodExecRestoreHook{
|
||||
"container1": {
|
||||
{
|
||||
|
||||
154
internal/resourcepolicies/resource_policies.go
Normal file
154
internal/resourcepolicies/resource_policies.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package resourcepolicies
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
type VolumeActionType string
|
||||
|
||||
const (
|
||||
// currently only support configmap type of resource config
|
||||
ConfigmapRefType string = "configmap"
|
||||
Skip VolumeActionType = "skip"
|
||||
)
|
||||
|
||||
// Action defined as one action for a specific way of backup
|
||||
type Action struct {
|
||||
// Type defined specific type of action, currently only support 'skip'
|
||||
Type VolumeActionType `yaml:"type"`
|
||||
// Parameters defined map of parameters when executing a specific action
|
||||
Parameters map[string]interface{} `yaml:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// volumePolicy defined policy to conditions to match Volumes and related action to handle matched Volumes
|
||||
type volumePolicy struct {
|
||||
// Conditions defined list of conditions to match Volumes
|
||||
Conditions map[string]interface{} `yaml:"conditions"`
|
||||
Action Action `yaml:"action"`
|
||||
}
|
||||
|
||||
// resourcePolicies currently defined slice of volume policies to handle backup
|
||||
type resourcePolicies struct {
|
||||
Version string `yaml:"version"`
|
||||
VolumePolicies []volumePolicy `yaml:"volumePolicies"`
|
||||
// we may support other resource policies in the future, and they could be added separately
|
||||
// OtherResourcePolicies []OtherResourcePolicy
|
||||
}
|
||||
|
||||
type Policies struct {
|
||||
version string
|
||||
volumePolicies []volPolicy
|
||||
// OtherPolicies
|
||||
}
|
||||
|
||||
func unmarshalResourcePolicies(yamlData *string) (*resourcePolicies, error) {
|
||||
resPolicies := &resourcePolicies{}
|
||||
if err := decodeStruct(strings.NewReader(*yamlData), resPolicies); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode yaml data into resource policies %v", err)
|
||||
} else {
|
||||
return resPolicies, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (policies *Policies) buildPolicy(resPolicies *resourcePolicies) error {
|
||||
for _, vp := range resPolicies.VolumePolicies {
|
||||
con, err := unmarshalVolConditions(vp.Conditions)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
volCap, err := parseCapacity(con.Capacity)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
var p volPolicy
|
||||
p.action = vp.Action
|
||||
p.conditions = append(p.conditions, &capacityCondition{capacity: *volCap})
|
||||
p.conditions = append(p.conditions, &storageClassCondition{storageClass: con.StorageClass})
|
||||
p.conditions = append(p.conditions, &nfsCondition{nfs: con.NFS})
|
||||
p.conditions = append(p.conditions, &csiCondition{csi: con.CSI})
|
||||
policies.volumePolicies = append(policies.volumePolicies, p)
|
||||
}
|
||||
|
||||
// Other resource policies
|
||||
|
||||
policies.version = resPolicies.Version
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Policies) match(res *structuredVolume) *Action {
|
||||
for _, policy := range p.volumePolicies {
|
||||
isAllMatch := false
|
||||
for _, con := range policy.conditions {
|
||||
if !con.match(res) {
|
||||
isAllMatch = false
|
||||
break
|
||||
}
|
||||
isAllMatch = true
|
||||
}
|
||||
if isAllMatch {
|
||||
return &policy.action
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Policies) GetMatchAction(res interface{}) (*Action, error) {
|
||||
volume := &structuredVolume{}
|
||||
switch obj := res.(type) {
|
||||
case *v1.PersistentVolume:
|
||||
volume.parsePV(obj)
|
||||
case *v1.Volume:
|
||||
volume.parsePodVolume(obj)
|
||||
default:
|
||||
return nil, errors.New("failed to convert object")
|
||||
}
|
||||
return p.match(volume), nil
|
||||
}
|
||||
|
||||
func (p *Policies) Validate() error {
|
||||
if p.version != currentSupportDataVersion {
|
||||
return fmt.Errorf("incompatible version number %s with supported version %s", p.version, currentSupportDataVersion)
|
||||
}
|
||||
|
||||
for _, policy := range p.volumePolicies {
|
||||
if err := policy.action.validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
for _, con := range policy.conditions {
|
||||
if err := con.validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetResourcePoliciesFromConfig(cm *v1.ConfigMap) (*Policies, error) {
|
||||
if cm == nil {
|
||||
return nil, fmt.Errorf("could not parse config from nil configmap")
|
||||
}
|
||||
if len(cm.Data) != 1 {
|
||||
return nil, fmt.Errorf("illegal resource policies %s/%s configmap", cm.Name, cm.Namespace)
|
||||
}
|
||||
|
||||
var yamlData string
|
||||
for _, v := range cm.Data {
|
||||
yamlData = v
|
||||
}
|
||||
|
||||
resPolicies, err := unmarshalResourcePolicies(&yamlData)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
policies := &Policies{}
|
||||
if err := policies.buildPolicy(resPolicies); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
381
internal/resourcepolicies/resource_policies_test.go
Normal file
381
internal/resourcepolicies/resource_policies_test.go
Normal file
@@ -0,0 +1,381 @@
|
||||
package resourcepolicies
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestLoadResourcePolicies(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
yamlData string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "unknown key in yaml",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
unknown: {}
|
||||
storageClass:
|
||||
- gp2
|
||||
- ebs-sc
|
||||
action:
|
||||
type: skip`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "reduplicated key in yaml",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
capacity: "0,100Gi"
|
||||
storageClass:
|
||||
- gp2
|
||||
- ebs-sc
|
||||
action:
|
||||
type: skip`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error format of storageClass",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
storageClass: gp2
|
||||
action:
|
||||
type: skip`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error format of csi",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
csi: gp2
|
||||
action:
|
||||
type: skip`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error format of nfs",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
csi: {}
|
||||
nfs: abc
|
||||
action:
|
||||
type: skip`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "supported formart volume policies",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
csi:
|
||||
driver: aws.efs.csi.driver
|
||||
nfs: {}
|
||||
storageClass:
|
||||
- gp2
|
||||
- ebs-sc
|
||||
action:
|
||||
type: skip`,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := unmarshalResourcePolicies(&tc.yamlData)
|
||||
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Fatalf("Expected error %v, but got error %v", tc.wantErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetResourceMatchedAction(t *testing.T) {
|
||||
resPolicies := &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: Action{Type: "volume-snapshot"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "10,100Gi",
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: Action{Type: "file-system-backup"},
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
volume *structuredVolume
|
||||
expectedAction *Action
|
||||
}{
|
||||
{
|
||||
name: "match policy",
|
||||
volume: &structuredVolume{
|
||||
capacity: *resource.NewQuantity(5<<30, resource.BinarySI),
|
||||
storageClass: "ebs-sc",
|
||||
csi: &csiVolumeSource{Driver: "aws.efs.csi.driver"},
|
||||
},
|
||||
expectedAction: &Action{Type: "skip"},
|
||||
},
|
||||
{
|
||||
name: "both matches return the first policy",
|
||||
volume: &structuredVolume{
|
||||
capacity: *resource.NewQuantity(50<<30, resource.BinarySI),
|
||||
storageClass: "ebs-sc",
|
||||
csi: &csiVolumeSource{Driver: "aws.efs.csi.driver"},
|
||||
},
|
||||
expectedAction: &Action{Type: "volume-snapshot"},
|
||||
},
|
||||
{
|
||||
name: "dismatch all policies",
|
||||
volume: &structuredVolume{
|
||||
capacity: *resource.NewQuantity(50<<30, resource.BinarySI),
|
||||
storageClass: "ebs-sc",
|
||||
nfs: &nFSVolumeSource{},
|
||||
},
|
||||
expectedAction: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
policies := &Policies{}
|
||||
err := policies.buildPolicy(resPolicies)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to build policy with error %v", err)
|
||||
}
|
||||
|
||||
action := policies.match(tc.volume)
|
||||
if action == nil {
|
||||
if tc.expectedAction != nil {
|
||||
t.Errorf("Expected action %v, but got result nil", tc.expectedAction.Type)
|
||||
}
|
||||
} else {
|
||||
if tc.expectedAction != nil {
|
||||
if action.Type != tc.expectedAction.Type {
|
||||
t.Errorf("Expected action %v, but got result %v", tc.expectedAction.Type, action.Type)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("Expected action nil, but got result %v", action.Type)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetResourcePoliciesFromConfig(t *testing.T) {
|
||||
// Create a test ConfigMap
|
||||
cm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-configmap",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"test-data": "version: v1\nvolumePolicies:\n- conditions:\n capacity: '0,10Gi'\n action:\n type: skip",
|
||||
},
|
||||
}
|
||||
|
||||
// Call the function and check for errors
|
||||
resPolicies, err := GetResourcePoliciesFromConfig(cm)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Check that the returned resourcePolicies object contains the expected data
|
||||
assert.Equal(t, "v1", resPolicies.version)
|
||||
assert.Len(t, resPolicies.volumePolicies, 1)
|
||||
policies := resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
},
|
||||
Action: Action{
|
||||
Type: Skip,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
p := &Policies{}
|
||||
err = p.buildPolicy(&policies)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build policy with error %v", err)
|
||||
}
|
||||
assert.Equal(t, p, resPolicies)
|
||||
}
|
||||
|
||||
func TestGetMatchAction(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
yamlData string
|
||||
vol *v1.PersistentVolume
|
||||
skip bool
|
||||
}{
|
||||
{
|
||||
name: "empty csi",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
csi: {}
|
||||
action:
|
||||
type: skip`,
|
||||
vol: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
CSI: &v1.CSIPersistentVolumeSource{Driver: "aws.ebs.csi.driver"},
|
||||
}},
|
||||
},
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "empty csi with pv no csi driver",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
csi: {}
|
||||
action:
|
||||
type: skip`,
|
||||
vol: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceStorage: resource.MustParse("1Gi"),
|
||||
}},
|
||||
},
|
||||
skip: false,
|
||||
},
|
||||
{
|
||||
name: "csi not configured",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
action:
|
||||
type: skip`,
|
||||
vol: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
CSI: &v1.CSIPersistentVolumeSource{Driver: "aws.ebs.csi.driver"},
|
||||
}},
|
||||
},
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "empty nfs",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
nfs: {}
|
||||
action:
|
||||
type: skip`,
|
||||
vol: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
NFS: &v1.NFSVolumeSource{Server: "192.168.1.20"},
|
||||
}},
|
||||
},
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "nfs not configured",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
action:
|
||||
type: skip`,
|
||||
vol: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
NFS: &v1.NFSVolumeSource{Server: "192.168.1.20"},
|
||||
},
|
||||
},
|
||||
},
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "empty nfs with pv no nfs volume source",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
capacity: "0,100Gi"
|
||||
nfs: {}
|
||||
action:
|
||||
type: skip`,
|
||||
vol: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
skip: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
resPolicies, err := unmarshalResourcePolicies(&tc.yamlData)
|
||||
if err != nil {
|
||||
t.Fatalf("got error when get match action %v", err)
|
||||
}
|
||||
assert.Nil(t, err)
|
||||
policies := &Policies{}
|
||||
err = policies.buildPolicy(resPolicies)
|
||||
assert.Nil(t, err)
|
||||
action, err := policies.GetMatchAction(tc.vol)
|
||||
assert.Nil(t, err)
|
||||
|
||||
if tc.skip {
|
||||
if action.Type != Skip {
|
||||
t.Fatalf("Expected action skip but is %v", action.Type)
|
||||
}
|
||||
} else if action != nil && action.Type == Skip {
|
||||
t.Fatalf("Expected action not skip but is %v", action.Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
205
internal/resourcepolicies/volume_resources.go
Normal file
205
internal/resourcepolicies/volume_resources.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package resourcepolicies
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
type volPolicy struct {
|
||||
action Action
|
||||
conditions []volumeCondition
|
||||
}
|
||||
|
||||
type volumeCondition interface {
|
||||
match(v *structuredVolume) bool
|
||||
validate() error
|
||||
}
|
||||
|
||||
// capacity consist of the lower and upper boundary
|
||||
type capacity struct {
|
||||
lower resource.Quantity
|
||||
upper resource.Quantity
|
||||
}
|
||||
|
||||
type structuredVolume struct {
|
||||
capacity resource.Quantity
|
||||
storageClass string
|
||||
nfs *nFSVolumeSource
|
||||
csi *csiVolumeSource
|
||||
}
|
||||
|
||||
func (s *structuredVolume) parsePV(pv *corev1api.PersistentVolume) {
|
||||
s.capacity = *pv.Spec.Capacity.Storage()
|
||||
s.storageClass = pv.Spec.StorageClassName
|
||||
nfs := pv.Spec.NFS
|
||||
if nfs != nil {
|
||||
s.nfs = &nFSVolumeSource{Server: nfs.Server, Path: nfs.Path}
|
||||
}
|
||||
|
||||
csi := pv.Spec.CSI
|
||||
if csi != nil {
|
||||
s.csi = &csiVolumeSource{Driver: csi.Driver}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *structuredVolume) parsePodVolume(vol *corev1api.Volume) {
|
||||
nfs := vol.NFS
|
||||
if nfs != nil {
|
||||
s.nfs = &nFSVolumeSource{Server: nfs.Server, Path: nfs.Path}
|
||||
}
|
||||
|
||||
csi := vol.CSI
|
||||
if csi != nil {
|
||||
s.csi = &csiVolumeSource{Driver: csi.Driver}
|
||||
}
|
||||
}
|
||||
|
||||
type capacityCondition struct {
|
||||
capacity capacity
|
||||
}
|
||||
|
||||
func (c *capacityCondition) match(v *structuredVolume) bool {
|
||||
return c.capacity.isInRange(v.capacity)
|
||||
}
|
||||
|
||||
type storageClassCondition struct {
|
||||
storageClass []string
|
||||
}
|
||||
|
||||
func (s *storageClassCondition) match(v *structuredVolume) bool {
|
||||
if len(s.storageClass) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if v.storageClass == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, sc := range s.storageClass {
|
||||
if v.storageClass == sc {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type nfsCondition struct {
|
||||
nfs *nFSVolumeSource
|
||||
}
|
||||
|
||||
func (c *nfsCondition) match(v *structuredVolume) bool {
|
||||
if c.nfs == nil {
|
||||
return true
|
||||
}
|
||||
if v.nfs == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.nfs.Path == "" {
|
||||
if c.nfs.Server == "" { // match nfs: {}
|
||||
return v.nfs != nil
|
||||
}
|
||||
if c.nfs.Server != v.nfs.Server {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if c.nfs.Path != v.nfs.Path {
|
||||
return false
|
||||
}
|
||||
if c.nfs.Server == "" {
|
||||
return true
|
||||
}
|
||||
if c.nfs.Server != v.nfs.Server {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
type csiCondition struct {
|
||||
csi *csiVolumeSource
|
||||
}
|
||||
|
||||
func (c *csiCondition) match(v *structuredVolume) bool {
|
||||
if c.csi == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if c.csi.Driver == "" { // match csi: {}
|
||||
return v.csi != nil
|
||||
}
|
||||
|
||||
if v.csi == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return c.csi.Driver == v.csi.Driver
|
||||
}
|
||||
|
||||
// parseCapacity parse string into capacity format
|
||||
func parseCapacity(cap string) (*capacity, error) {
|
||||
if cap == "" {
|
||||
cap = ","
|
||||
}
|
||||
capacities := strings.Split(cap, ",")
|
||||
var quantities []resource.Quantity
|
||||
if len(capacities) != 2 {
|
||||
return nil, fmt.Errorf("wrong format of Capacity %v", cap)
|
||||
} else {
|
||||
for _, v := range capacities {
|
||||
if strings.TrimSpace(v) == "" {
|
||||
// case similar "10Gi,"
|
||||
// if empty, the quantity will assigned with 0
|
||||
quantities = append(quantities, *resource.NewQuantity(int64(0), resource.DecimalSI))
|
||||
} else {
|
||||
if quantity, err := resource.ParseQuantity(strings.TrimSpace(v)); err != nil {
|
||||
return nil, fmt.Errorf("wrong format of Capacity %v with err %v", v, err)
|
||||
} else {
|
||||
quantities = append(quantities, quantity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &capacity{lower: quantities[0], upper: quantities[1]}, nil
|
||||
}
|
||||
|
||||
// isInRange returns true if the quantity y is in range of capacity, or it returns false
|
||||
func (c *capacity) isInRange(y resource.Quantity) bool {
|
||||
if c.lower.IsZero() && c.upper.Cmp(y) >= 0 {
|
||||
// [0, a] y
|
||||
return true
|
||||
}
|
||||
if c.upper.IsZero() && c.lower.Cmp(y) <= 0 {
|
||||
// [b, 0] y
|
||||
return true
|
||||
}
|
||||
if !c.lower.IsZero() && !c.upper.IsZero() {
|
||||
// [a, b] y
|
||||
return c.lower.Cmp(y) <= 0 && c.upper.Cmp(y) >= 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// unmarshalVolConditions parse map[string]interface{} into volumeConditions format
|
||||
// and validate key fields of the map.
|
||||
func unmarshalVolConditions(con map[string]interface{}) (*volumeConditions, error) {
|
||||
volConditons := &volumeConditions{}
|
||||
buffer := new(bytes.Buffer)
|
||||
err := yaml.NewEncoder(buffer).Encode(con)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to encode volume conditions")
|
||||
}
|
||||
|
||||
if err := decodeStruct(buffer, volConditons); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode volume conditions")
|
||||
}
|
||||
return volConditons, nil
|
||||
}
|
||||
412
internal/resourcepolicies/volume_resources_test.go
Normal file
412
internal/resourcepolicies/volume_resources_test.go
Normal file
@@ -0,0 +1,412 @@
|
||||
package resourcepolicies
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func setStructuredVolume(capacity resource.Quantity, sc string, nfs *nFSVolumeSource, csi *csiVolumeSource) *structuredVolume {
|
||||
return &structuredVolume{
|
||||
capacity: capacity,
|
||||
storageClass: sc,
|
||||
nfs: nfs,
|
||||
csi: csi,
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCapacity(t *testing.T) {
|
||||
var emptyCapacity capacity
|
||||
tests := []struct {
|
||||
input string
|
||||
expected capacity
|
||||
expectedErr error
|
||||
}{
|
||||
{"10Gi,20Gi", capacity{lower: *resource.NewQuantity(10<<30, resource.BinarySI), upper: *resource.NewQuantity(20<<30, resource.BinarySI)}, nil},
|
||||
{"10Gi,", capacity{lower: *resource.NewQuantity(10<<30, resource.BinarySI), upper: *resource.NewQuantity(0, resource.DecimalSI)}, nil},
|
||||
{"10Gi", emptyCapacity, fmt.Errorf("wrong format of Capacity 10Gi")},
|
||||
{"", emptyCapacity, nil},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test // capture range variable
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
actual, actualErr := parseCapacity(test.input)
|
||||
if test.expected != emptyCapacity {
|
||||
assert.Equal(t, test.expected.lower.Cmp(actual.lower), 0)
|
||||
assert.Equal(t, test.expected.upper.Cmp(actual.upper), 0)
|
||||
}
|
||||
assert.Equal(t, test.expectedErr, actualErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapacityIsInRange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
capacity *capacity
|
||||
quantity resource.Quantity
|
||||
isInRange bool
|
||||
}{
|
||||
{&capacity{*resource.NewQuantity(0, resource.BinarySI), *resource.NewQuantity(10<<30, resource.BinarySI)}, *resource.NewQuantity(5<<30, resource.BinarySI), true},
|
||||
{&capacity{*resource.NewQuantity(0, resource.BinarySI), *resource.NewQuantity(10<<30, resource.BinarySI)}, *resource.NewQuantity(15<<30, resource.BinarySI), false},
|
||||
{&capacity{*resource.NewQuantity(20<<30, resource.BinarySI), *resource.NewQuantity(0, resource.DecimalSI)}, *resource.NewQuantity(25<<30, resource.BinarySI), true},
|
||||
{&capacity{*resource.NewQuantity(20<<30, resource.BinarySI), *resource.NewQuantity(0, resource.DecimalSI)}, *resource.NewQuantity(15<<30, resource.BinarySI), false},
|
||||
{&capacity{*resource.NewQuantity(10<<30, resource.BinarySI), *resource.NewQuantity(20<<30, resource.BinarySI)}, *resource.NewQuantity(15<<30, resource.BinarySI), true},
|
||||
{&capacity{*resource.NewQuantity(10<<30, resource.BinarySI), *resource.NewQuantity(20<<30, resource.BinarySI)}, *resource.NewQuantity(5<<30, resource.BinarySI), false},
|
||||
{&capacity{*resource.NewQuantity(10<<30, resource.BinarySI), *resource.NewQuantity(20<<30, resource.BinarySI)}, *resource.NewQuantity(25<<30, resource.BinarySI), false},
|
||||
{&capacity{*resource.NewQuantity(0, resource.BinarySI), *resource.NewQuantity(0, resource.BinarySI)}, *resource.NewQuantity(5<<30, resource.BinarySI), true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test // capture range variable
|
||||
t.Run(fmt.Sprintf("%v with %v", test.capacity, test.quantity), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := test.capacity.isInRange(test.quantity)
|
||||
|
||||
assert.Equal(t, test.isInRange, actual)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageClassConditionMatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
condition *storageClassCondition
|
||||
volume *structuredVolume
|
||||
expectedMatch bool
|
||||
}{
|
||||
{
|
||||
name: "match single storage class",
|
||||
condition: &storageClassCondition{[]string{"gp2"}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "gp2", nil, nil),
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "match multiple storage classes",
|
||||
condition: &storageClassCondition{[]string{"gp2", "ebs-sc"}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "gp2", nil, nil),
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "mismatch storage class",
|
||||
condition: &storageClassCondition{[]string{"gp2"}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "ebs-sc", nil, nil),
|
||||
expectedMatch: false,
|
||||
},
|
||||
{
|
||||
name: "empty storage class",
|
||||
condition: &storageClassCondition{[]string{}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "ebs-sc", nil, nil),
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "empty volume storage class",
|
||||
condition: &storageClassCondition{[]string{"gp2"}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, nil),
|
||||
expectedMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
match := tt.condition.match(tt.volume)
|
||||
if match != tt.expectedMatch {
|
||||
t.Errorf("expected %v, but got %v", tt.expectedMatch, match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNFSConditionMatch(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
condition *nfsCondition
|
||||
volume *structuredVolume
|
||||
expectedMatch bool
|
||||
}{
|
||||
{
|
||||
name: "match nfs condition",
|
||||
condition: &nfsCondition{&nFSVolumeSource{Server: "192.168.10.20"}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", &nFSVolumeSource{Server: "192.168.10.20"}, nil),
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "empty nfs condition",
|
||||
condition: &nfsCondition{nil},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", &nFSVolumeSource{Server: "192.168.10.20"}, nil),
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "empty nfs server and path condition",
|
||||
condition: &nfsCondition{&nFSVolumeSource{Server: "", Path: ""}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", &nFSVolumeSource{Server: "192.168.10.20"}, nil),
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "server dismatch",
|
||||
condition: &nfsCondition{&nFSVolumeSource{Server: "192.168.10.20", Path: ""}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", &nFSVolumeSource{Server: ""}, nil),
|
||||
expectedMatch: false,
|
||||
},
|
||||
{
|
||||
name: "empty nfs server condition",
|
||||
condition: &nfsCondition{&nFSVolumeSource{Path: "/mnt/data"}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", &nFSVolumeSource{Server: "192.168.10.20", Path: "/mnt/data"}, nil),
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "empty nfs volume",
|
||||
condition: &nfsCondition{&nFSVolumeSource{Server: "192.168.10.20"}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, nil),
|
||||
expectedMatch: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
match := tt.condition.match(tt.volume)
|
||||
if match != tt.expectedMatch {
|
||||
t.Errorf("expected %v, but got %v", tt.expectedMatch, match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCSIConditionMatch(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
condition *csiCondition
|
||||
volume *structuredVolume
|
||||
expectedMatch bool
|
||||
}{
|
||||
{
|
||||
name: "match csi condition",
|
||||
condition: &csiCondition{&csiVolumeSource{Driver: "test"}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, &csiVolumeSource{Driver: "test"}),
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "empty csi condition",
|
||||
condition: &csiCondition{nil},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, &csiVolumeSource{Driver: "test"}),
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "empty csi volume",
|
||||
condition: &csiCondition{&csiVolumeSource{Driver: "test"}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, &csiVolumeSource{}),
|
||||
expectedMatch: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
match := tt.condition.match(tt.volume)
|
||||
if match != tt.expectedMatch {
|
||||
t.Errorf("expected %v, but got %v", tt.expectedMatch, match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalVolumeConditions(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input map[string]interface{}
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "Valid input",
|
||||
input: map[string]interface{}{
|
||||
"capacity": "1Gi,10Gi",
|
||||
"storageClass": []string{
|
||||
"gp2",
|
||||
"ebs-sc",
|
||||
},
|
||||
"csi": &csiVolumeSource{
|
||||
Driver: "aws.efs.csi.driver",
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Invalid input: invalid capacity filed name",
|
||||
input: map[string]interface{}{
|
||||
"Capacity": "1Gi,10Gi",
|
||||
},
|
||||
expectedError: "field Capacity not found",
|
||||
},
|
||||
{
|
||||
name: "Invalid input: invalid storage class format",
|
||||
input: map[string]interface{}{
|
||||
"storageClass": "ebs-sc",
|
||||
},
|
||||
expectedError: "str `ebs-sc` into []string",
|
||||
},
|
||||
{
|
||||
name: "Invalid input: invalid csi format",
|
||||
input: map[string]interface{}{
|
||||
"csi": "csi.driver",
|
||||
},
|
||||
expectedError: "str `csi.driver` into resourcepolicies.csiVolumeSource",
|
||||
},
|
||||
{
|
||||
name: "Invalid input: unknown field",
|
||||
input: map[string]interface{}{
|
||||
"unknown": "foo",
|
||||
},
|
||||
expectedError: "field unknown not found in type",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := unmarshalVolConditions(tc.input)
|
||||
if tc.expectedError != "" {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error '%s', but got nil", tc.expectedError)
|
||||
} else if !strings.Contains(err.Error(), tc.expectedError) {
|
||||
t.Errorf("Expected error '%s', but got '%v'", tc.expectedError, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePodVolume(t *testing.T) {
|
||||
// Mock data
|
||||
nfsVolume := corev1api.Volume{}
|
||||
nfsVolume.NFS = &corev1api.NFSVolumeSource{
|
||||
Server: "nfs.example.com",
|
||||
Path: "/exports/data",
|
||||
}
|
||||
csiVolume := corev1api.Volume{}
|
||||
csiVolume.CSI = &corev1api.CSIVolumeSource{
|
||||
Driver: "csi.example.com",
|
||||
}
|
||||
emptyVolume := corev1api.Volume{}
|
||||
|
||||
// Test cases
|
||||
testCases := []struct {
|
||||
name string
|
||||
inputVolume *corev1api.Volume
|
||||
expectedNFS *nFSVolumeSource
|
||||
expectedCSI *csiVolumeSource
|
||||
}{
|
||||
{
|
||||
name: "NFS volume",
|
||||
inputVolume: &nfsVolume,
|
||||
expectedNFS: &nFSVolumeSource{Server: "nfs.example.com", Path: "/exports/data"},
|
||||
},
|
||||
{
|
||||
name: "CSI volume",
|
||||
inputVolume: &csiVolume,
|
||||
expectedCSI: &csiVolumeSource{Driver: "csi.example.com"},
|
||||
},
|
||||
{
|
||||
name: "Empty volume",
|
||||
inputVolume: &emptyVolume,
|
||||
expectedNFS: nil,
|
||||
expectedCSI: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Call the function
|
||||
structuredVolume := &structuredVolume{}
|
||||
structuredVolume.parsePodVolume(tc.inputVolume)
|
||||
|
||||
// Check the results
|
||||
if tc.expectedNFS != nil {
|
||||
if structuredVolume.nfs == nil {
|
||||
t.Errorf("Expected a non-nil NFS volume source")
|
||||
} else if *tc.expectedNFS != *structuredVolume.nfs {
|
||||
t.Errorf("NFS volume source does not match expected value")
|
||||
}
|
||||
}
|
||||
if tc.expectedCSI != nil {
|
||||
if structuredVolume.csi == nil {
|
||||
t.Errorf("Expected a non-nil CSI volume source")
|
||||
} else if *tc.expectedCSI != *structuredVolume.csi {
|
||||
t.Errorf("CSI volume source does not match expected value")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePV(t *testing.T) {
|
||||
// Mock data
|
||||
nfsVolume := corev1api.PersistentVolume{}
|
||||
nfsVolume.Spec.Capacity = corev1api.ResourceList{corev1api.ResourceStorage: resource.MustParse("1Gi")}
|
||||
nfsVolume.Spec.NFS = &corev1api.NFSVolumeSource{Server: "nfs.example.com", Path: "/exports/data"}
|
||||
csiVolume := corev1api.PersistentVolume{}
|
||||
csiVolume.Spec.Capacity = corev1api.ResourceList{corev1api.ResourceStorage: resource.MustParse("2Gi")}
|
||||
csiVolume.Spec.CSI = &corev1api.CSIPersistentVolumeSource{Driver: "csi.example.com"}
|
||||
emptyVolume := corev1api.PersistentVolume{}
|
||||
|
||||
// Test cases
|
||||
testCases := []struct {
|
||||
name string
|
||||
inputVolume *corev1api.PersistentVolume
|
||||
expectedNFS *nFSVolumeSource
|
||||
expectedCSI *csiVolumeSource
|
||||
}{
|
||||
{
|
||||
name: "NFS volume",
|
||||
inputVolume: &nfsVolume,
|
||||
expectedNFS: &nFSVolumeSource{Server: "nfs.example.com", Path: "/exports/data"},
|
||||
expectedCSI: nil,
|
||||
},
|
||||
{
|
||||
name: "CSI volume",
|
||||
inputVolume: &csiVolume,
|
||||
expectedNFS: nil,
|
||||
expectedCSI: &csiVolumeSource{Driver: "csi.example.com"},
|
||||
},
|
||||
{
|
||||
name: "Empty volume",
|
||||
inputVolume: &emptyVolume,
|
||||
expectedNFS: nil,
|
||||
expectedCSI: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Call the function
|
||||
structuredVolume := &structuredVolume{}
|
||||
structuredVolume.parsePV(tc.inputVolume)
|
||||
// Check the results
|
||||
if structuredVolume.capacity != *tc.inputVolume.Spec.Capacity.Storage() {
|
||||
t.Errorf("capacity does not match expected value")
|
||||
}
|
||||
if structuredVolume.storageClass != tc.inputVolume.Spec.StorageClassName {
|
||||
t.Errorf("Storage class does not match expected value")
|
||||
}
|
||||
if tc.expectedNFS != nil {
|
||||
if structuredVolume.nfs == nil {
|
||||
t.Errorf("Expected a non-nil NFS volume source")
|
||||
} else if *tc.expectedNFS != *structuredVolume.nfs {
|
||||
t.Errorf("NFS volume source does not match expected value")
|
||||
}
|
||||
}
|
||||
if tc.expectedCSI != nil {
|
||||
if structuredVolume.csi == nil {
|
||||
t.Errorf("Expected a non-nil CSI volume source")
|
||||
} else if *tc.expectedCSI != *structuredVolume.csi {
|
||||
t.Errorf("CSI volume source does not match expected value")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
76
internal/resourcepolicies/volume_resources_validator.go
Normal file
76
internal/resourcepolicies/volume_resources_validator.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package resourcepolicies
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const currentSupportDataVersion = "v1"
|
||||
|
||||
type csiVolumeSource struct {
|
||||
Driver string `yaml:"driver,omitempty"`
|
||||
}
|
||||
|
||||
type nFSVolumeSource struct {
|
||||
// Server is the hostname or IP address of the NFS server
|
||||
Server string `yaml:"server,omitempty"`
|
||||
// Path is the exported NFS share
|
||||
Path string `yaml:"path,omitempty"`
|
||||
}
|
||||
|
||||
// volumeConditions defined the current format of conditions we parsed
|
||||
type volumeConditions struct {
|
||||
Capacity string `yaml:"capacity,omitempty"`
|
||||
StorageClass []string `yaml:"storageClass,omitempty"`
|
||||
NFS *nFSVolumeSource `yaml:"nfs,omitempty"`
|
||||
CSI *csiVolumeSource `yaml:"csi,omitempty"`
|
||||
}
|
||||
|
||||
func (c *capacityCondition) validate() error {
|
||||
// [0, a]
|
||||
// [a, b]
|
||||
// [b, 0]
|
||||
// ==> low <= upper or upper is zero
|
||||
if (c.capacity.upper.Cmp(c.capacity.lower) >= 0) ||
|
||||
(!c.capacity.lower.IsZero() && c.capacity.upper.IsZero()) {
|
||||
return nil
|
||||
}
|
||||
return errors.Errorf("illegal values for capacity %v", c.capacity)
|
||||
|
||||
}
|
||||
|
||||
func (s *storageClassCondition) validate() error {
|
||||
// validate by yamlv3
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *nfsCondition) validate() error {
|
||||
// validate by yamlv3
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *csiCondition) validate() error {
|
||||
// validate by yamlv3
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeStruct restric validate the keys in decoded mappings to exist as fields in the struct being decoded into
|
||||
func decodeStruct(r io.Reader, s interface{}) error {
|
||||
dec := yaml.NewDecoder(r)
|
||||
dec.KnownFields(true)
|
||||
return dec.Decode(s)
|
||||
}
|
||||
|
||||
// validate check action format
|
||||
func (a *Action) validate() error {
|
||||
// validate Type
|
||||
if a.Type != Skip {
|
||||
return fmt.Errorf("invalid action type %s", a.Type)
|
||||
}
|
||||
|
||||
// TODO validate parameters
|
||||
return nil
|
||||
}
|
||||
251
internal/resourcepolicies/volume_resources_validator_test.go
Normal file
251
internal/resourcepolicies/volume_resources_validator_test.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package resourcepolicies
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func TestCapacityConditionValidate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
capacity *capacity
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "lower and upper are both zero",
|
||||
capacity: &capacity{lower: *resource.NewQuantity(0, resource.DecimalSI), upper: *resource.NewQuantity(0, resource.DecimalSI)},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "lower is zero and upper is greater than zero",
|
||||
capacity: &capacity{lower: *resource.NewQuantity(0, resource.DecimalSI), upper: *resource.NewQuantity(100, resource.DecimalSI)},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "lower is greater than upper",
|
||||
capacity: &capacity{lower: *resource.NewQuantity(100, resource.DecimalSI), upper: *resource.NewQuantity(50, resource.DecimalSI)},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "lower and upper are equal",
|
||||
capacity: &capacity{lower: *resource.NewQuantity(100, resource.DecimalSI), upper: *resource.NewQuantity(100, resource.DecimalSI)},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "lower is greater than zero and upper is zero",
|
||||
capacity: &capacity{lower: *resource.NewQuantity(100, resource.DecimalSI), upper: *resource.NewQuantity(0, resource.DecimalSI)},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "lower and upper are both not zero and lower is less than upper",
|
||||
capacity: &capacity{lower: *resource.NewQuantity(100, resource.DecimalSI), upper: *resource.NewQuantity(200, resource.DecimalSI)},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "lower and upper are both not zero and lower is equal to upper",
|
||||
capacity: &capacity{lower: *resource.NewQuantity(100, resource.DecimalSI), upper: *resource.NewQuantity(100, resource.DecimalSI)},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "lower and upper are both not zero and lower is greater than upper",
|
||||
capacity: &capacity{lower: *resource.NewQuantity(200, resource.DecimalSI), upper: *resource.NewQuantity(100, resource.DecimalSI)},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c := &capacityCondition{capacity: *tc.capacity}
|
||||
err := c.validate()
|
||||
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Fatalf("Expected error %v, but got error %v", tc.wantErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
res *resourcePolicies
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "unknown key in yaml",
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
"unknown": "",
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error format of capacity",
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "10Gi",
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error format of storageClass",
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
"storageClass": "ebs-sc",
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error format of csi",
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"csi": "aws.efs.csi.driver",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unsupported version",
|
||||
res: &resourcePolicies{
|
||||
Version: "v2",
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unsupported action",
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "unsupported"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error format of nfs",
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"nfs": "aws.efs.csi.driver",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "supported formart volume policies",
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
"nfs": interface{}(
|
||||
map[string]interface{}{
|
||||
"server": "192.168.20.90",
|
||||
"path": "/mnt/data/",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
policies := &Policies{}
|
||||
err1 := policies.buildPolicy(tc.res)
|
||||
err2 := policies.Validate()
|
||||
|
||||
if tc.wantErr {
|
||||
if err1 == nil && err2 == nil {
|
||||
t.Fatalf("Expected error %v, but not get error", tc.wantErr)
|
||||
}
|
||||
} else {
|
||||
if err1 != nil || err2 != nil {
|
||||
t.Fatalf("Expected error %v, but got error %v %v", tc.wantErr, err1, err2)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ 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
|
||||
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,
|
||||
|
||||
@@ -71,10 +71,7 @@ func IsReadyToValidate(bslValidationFrequency *metav1.Duration, lastValidationTi
|
||||
|
||||
// We want to validate BSL only if the set validation frequency/ interval has elapsed.
|
||||
nextValidation := lastValidation.Add(validationFrequency) // next validation time: last validation time + validation frequency
|
||||
if time.Now().UTC().Before(nextValidation) { // ready only when NOW is equal to or after the next validation time
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return !time.Now().UTC().Before(nextValidation) // ready only when NOW is equal to or after the next validation time
|
||||
}
|
||||
|
||||
// ListBackupStorageLocations verifies if there are any backup storage locations.
|
||||
|
||||
@@ -163,7 +163,7 @@ func TestListBackupStorageLocations(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
client := fake.NewFakeClientWithScheme(scheme.Scheme, tt.backupLocations)
|
||||
client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(tt.backupLocations).Build()
|
||||
if tt.expectError {
|
||||
_, err := ListBackupStorageLocations(context.Background(), client, "ns-1")
|
||||
g.Expect(err).NotTo(BeNil())
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package v1
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@@ -52,6 +53,38 @@ type BackupSpec struct {
|
||||
// +nullable
|
||||
ExcludedResources []string `json:"excludedResources,omitempty"`
|
||||
|
||||
// IncludedClusterScopedResources is a slice of cluster-scoped
|
||||
// resource type names to include in the backup.
|
||||
// If set to "*", all cluster-scoped resource types are included.
|
||||
// The default value is empty, which means only related
|
||||
// cluster-scoped resources are included.
|
||||
// +optional
|
||||
// +nullable
|
||||
IncludedClusterScopedResources []string `json:"includedClusterScopedResources,omitempty"`
|
||||
|
||||
// ExcludedClusterScopedResources is a slice of cluster-scoped
|
||||
// resource type names to exclude from the backup.
|
||||
// If set to "*", all cluster-scoped resource types are excluded.
|
||||
// The default value is empty.
|
||||
// +optional
|
||||
// +nullable
|
||||
ExcludedClusterScopedResources []string `json:"excludedClusterScopedResources,omitempty"`
|
||||
|
||||
// IncludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
// resource type names to include in the backup.
|
||||
// The default value is "*".
|
||||
// +optional
|
||||
// +nullable
|
||||
IncludedNamespaceScopedResources []string `json:"includedNamespaceScopedResources,omitempty"`
|
||||
|
||||
// ExcludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
// resource type names to exclude from the backup.
|
||||
// If set to "*", all namespace-scoped resource types are excluded.
|
||||
// The default value is empty.
|
||||
// +optional
|
||||
// +nullable
|
||||
ExcludedNamespaceScopedResources []string `json:"excludedNamespaceScopedResources,omitempty"`
|
||||
|
||||
// LabelSelector is a metav1.LabelSelector to filter with
|
||||
// when adding individual objects to the backup. If empty
|
||||
// or nil, all objects are included. Optional.
|
||||
@@ -68,7 +101,7 @@ type BackupSpec struct {
|
||||
// +nullable
|
||||
OrLabelSelectors []*metav1.LabelSelector `json:"orLabelSelectors,omitempty"`
|
||||
|
||||
// SnapshotVolumes specifies whether to take cloud snapshots
|
||||
// SnapshotVolumes specifies whether to take snapshots
|
||||
// of any PV's referenced in the set of objects included
|
||||
// in the Backup.
|
||||
// +optional
|
||||
@@ -124,6 +157,14 @@ type BackupSpec struct {
|
||||
// The default value is 10 minute.
|
||||
// +optional
|
||||
CSISnapshotTimeout metav1.Duration `json:"csiSnapshotTimeout,omitempty"`
|
||||
|
||||
// ItemOperationTimeout specifies the time used to wait for asynchronous BackupItemAction operations
|
||||
// The default value is 1 hour.
|
||||
// +optional
|
||||
ItemOperationTimeout metav1.Duration `json:"itemOperationTimeout,omitempty"`
|
||||
// ResourcePolicy specifies the referenced resource policies that backup should follow
|
||||
// +optional
|
||||
ResourcePolicy *v1.TypedLocalObjectReference `json:"resourcePolicy,omitempty"`
|
||||
}
|
||||
|
||||
// BackupHooks contains custom behaviors that should be executed at different phases of the backup.
|
||||
@@ -221,7 +262,7 @@ const (
|
||||
|
||||
// BackupPhase is a string representation of the lifecycle phase
|
||||
// of a Velero backup.
|
||||
// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;Completed;PartiallyFailed;Failed;Deleting
|
||||
// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;WaitingForPluginOperations;WaitingForPluginOperationsPartiallyFailed;Finalizing;FinalizingPartiallyFailed;Completed;PartiallyFailed;Failed;Deleting
|
||||
type BackupPhase string
|
||||
|
||||
const (
|
||||
@@ -236,16 +277,37 @@ const (
|
||||
// BackupPhaseInProgress means the backup is currently executing.
|
||||
BackupPhaseInProgress BackupPhase = "InProgress"
|
||||
|
||||
// BackupPhaseUploading means the backups of Kubernetes resources
|
||||
// and creation of snapshots was successful and snapshot data
|
||||
// is currently uploading. The backup is not usable yet.
|
||||
BackupPhaseUploading BackupPhase = "Uploading"
|
||||
// BackupPhaseWaitingForPluginOperations means the backup of
|
||||
// Kubernetes resources, creation of snapshots, and other
|
||||
// async plugin operations was successful and snapshot data is
|
||||
// currently uploading or other plugin operations are still
|
||||
// ongoing. The backup is not usable yet.
|
||||
BackupPhaseWaitingForPluginOperations BackupPhase = "WaitingForPluginOperations"
|
||||
|
||||
// BackupPhaseUploadingPartialFailure means the backup of Kubernetes
|
||||
// resources and creation of snapshots partially failed (final phase
|
||||
// will be PartiallyFailed) and snapshot data is currently uploading.
|
||||
// BackupPhaseWaitingForPluginOperationsPartiallyFailed means
|
||||
// the backup of Kubernetes resources, creation of snapshots,
|
||||
// and other async plugin operations partially failed (final
|
||||
// phase will be PartiallyFailed) and snapshot data is
|
||||
// currently uploading or other plugin operations are still
|
||||
// ongoing. The backup is not usable yet.
|
||||
BackupPhaseWaitingForPluginOperationsPartiallyFailed BackupPhase = "WaitingForPluginOperationsPartiallyFailed"
|
||||
|
||||
// BackupPhaseFinalizing means the backup of
|
||||
// Kubernetes resources, creation of snapshots, and other
|
||||
// async plugin operations were successful and snapshot upload and
|
||||
// other plugin operations are now complete, but the Backup is awaiting
|
||||
// final update of resources modified during async operations.
|
||||
// The backup is not usable yet.
|
||||
BackupPhaseUploadingPartialFailure BackupPhase = "UploadingPartialFailure"
|
||||
BackupPhaseFinalizing BackupPhase = "Finalizing"
|
||||
|
||||
// BackupPhaseFinalizingPartiallyFailed means the backup of
|
||||
// Kubernetes resources, creation of snapshots, and other
|
||||
// async plugin operations were successful and snapshot upload and
|
||||
// other plugin operations are now complete, but one or more errors
|
||||
// occurred during backup or async operation processing, and the
|
||||
// Backup is awaiting final update of resources modified during async
|
||||
// operations. The backup is not usable yet.
|
||||
BackupPhaseFinalizingPartiallyFailed BackupPhase = "FinalizingPartiallyFailed"
|
||||
|
||||
// BackupPhaseCompleted means the backup has run successfully without
|
||||
// errors.
|
||||
@@ -347,6 +409,21 @@ type BackupStatus struct {
|
||||
// completed CSI VolumeSnapshots for this backup.
|
||||
// +optional
|
||||
CSIVolumeSnapshotsCompleted int `json:"csiVolumeSnapshotsCompleted,omitempty"`
|
||||
|
||||
// BackupItemOperationsAttempted is the total number of attempted
|
||||
// async BackupItemAction operations for this backup.
|
||||
// +optional
|
||||
BackupItemOperationsAttempted int `json:"backupItemOperationsAttempted,omitempty"`
|
||||
|
||||
// BackupItemOperationsCompleted is the total number of successfully completed
|
||||
// async BackupItemAction operations for this backup.
|
||||
// +optional
|
||||
BackupItemOperationsCompleted int `json:"backupItemOperationsCompleted,omitempty"`
|
||||
|
||||
// BackupItemOperationsFailed is the total number of async
|
||||
// BackupItemAction operations for this backup which ended with an error.
|
||||
// +optional
|
||||
BackupItemOperationsFailed int `json:"backupItemOperationsFailed,omitempty"`
|
||||
}
|
||||
|
||||
// BackupProgress stores information about the progress of a Backup's execution.
|
||||
@@ -366,6 +443,11 @@ type BackupProgress struct {
|
||||
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:object:generate=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=backups,verbs=create;delete;get;list;patch;update;watch
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=backups/status,verbs=get;update;patch
|
||||
|
||||
// Backup is a Velero resource that represents the capture of Kubernetes
|
||||
// cluster state at a point in time (API objects and associated volume state).
|
||||
@@ -46,8 +46,4 @@ const (
|
||||
|
||||
// APIGroupVersionsFeatureFlag is the feature flag string that defines whether or not to handle multiple API Group Versions
|
||||
APIGroupVersionsFeatureFlag = "EnableAPIGroupVersions"
|
||||
|
||||
// UploadProgressFeatureFlag is the feature flag string that defines whether or not upload progress monitoring is enabled
|
||||
// and whether or not ItemSnapshotters should be invoked
|
||||
UploadProgressFeatureFlag = "EnableUploadProgress"
|
||||
)
|
||||
|
||||
@@ -25,17 +25,20 @@ type DownloadRequestSpec struct {
|
||||
}
|
||||
|
||||
// DownloadTargetKind represents what type of file to download.
|
||||
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemSnapshots;BackupResourceList;RestoreLog;RestoreResults;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents
|
||||
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;BackupResults;RestoreLog;RestoreResults;RestoreResourceList;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents
|
||||
type DownloadTargetKind string
|
||||
|
||||
const (
|
||||
DownloadTargetKindBackupLog DownloadTargetKind = "BackupLog"
|
||||
DownloadTargetKindBackupContents DownloadTargetKind = "BackupContents"
|
||||
DownloadTargetKindBackupVolumeSnapshots DownloadTargetKind = "BackupVolumeSnapshots"
|
||||
DownloadTargetKindBackupItemSnapshots DownloadTargetKind = "BackupItemSnapshots"
|
||||
DownloadTargetKindBackupItemOperations DownloadTargetKind = "BackupItemOperations"
|
||||
DownloadTargetKindBackupResourceList DownloadTargetKind = "BackupResourceList"
|
||||
DownloadTargetKindBackupResults DownloadTargetKind = "BackupResults"
|
||||
DownloadTargetKindRestoreLog DownloadTargetKind = "RestoreLog"
|
||||
DownloadTargetKindRestoreResults DownloadTargetKind = "RestoreResults"
|
||||
DownloadTargetKindRestoreResourceList DownloadTargetKind = "RestoreResourceList"
|
||||
DownloadTargetKindRestoreItemOperations DownloadTargetKind = "RestoreItemOperations"
|
||||
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
|
||||
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"
|
||||
)
|
||||
|
||||
@@ -81,7 +81,7 @@ type RestoreSpec struct {
|
||||
OrLabelSelectors []*metav1.LabelSelector `json:"orLabelSelectors,omitempty"`
|
||||
|
||||
// RestorePVs specifies whether to restore all included
|
||||
// PVs from snapshot (via the cloudprovider).
|
||||
// PVs from snapshot
|
||||
// +optional
|
||||
// +nullable
|
||||
RestorePVs *bool `json:"restorePVs,omitempty"`
|
||||
@@ -108,10 +108,15 @@ type RestoreSpec struct {
|
||||
// +optional
|
||||
Hooks RestoreHooks `json:"hooks,omitempty"`
|
||||
|
||||
// ExistingResourcePolicy specifies the restore behaviour for the kubernetes resource to be restored
|
||||
// ExistingResourcePolicy specifies the restore behavior for the kubernetes resource to be restored
|
||||
// +optional
|
||||
// +nullable
|
||||
ExistingResourcePolicy PolicyType `json:"existingResourcePolicy,omitempty"`
|
||||
|
||||
// ItemOperationTimeout specifies the time used to wait for RestoreItemAction operations
|
||||
// The default value is 1 hour.
|
||||
// +optional
|
||||
ItemOperationTimeout metav1.Duration `json:"itemOperationTimeout,omitempty"`
|
||||
}
|
||||
|
||||
// RestoreHooks contains custom behaviors that should be executed during or post restore.
|
||||
@@ -220,7 +225,7 @@ type InitRestoreHook struct {
|
||||
|
||||
// RestorePhase is a string representation of the lifecycle phase
|
||||
// of a Velero restore
|
||||
// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;Completed;PartiallyFailed;Failed
|
||||
// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;WaitingForPluginOperations;WaitingForPluginOperationsPartiallyFailed;Completed;PartiallyFailed;Failed
|
||||
type RestorePhase string
|
||||
|
||||
const (
|
||||
@@ -235,6 +240,19 @@ const (
|
||||
// RestorePhaseInProgress means the restore is currently executing.
|
||||
RestorePhaseInProgress RestorePhase = "InProgress"
|
||||
|
||||
// RestorePhaseWaitingForPluginOperations means the restore of
|
||||
// Kubernetes resources and other async plugin operations was
|
||||
// successful and plugin operations are still ongoing. The
|
||||
// restore is not complete yet.
|
||||
RestorePhaseWaitingForPluginOperations RestorePhase = "WaitingForPluginOperations"
|
||||
|
||||
// RestorePhaseWaitingForPluginOperationsPartiallyFailed means
|
||||
// the restore of Kubernetes resources and other async plugin
|
||||
// operations partially failed (final phase will be
|
||||
// PartiallyFailed) and other plugin operations are still
|
||||
// ongoing. The restore is not complete yet.
|
||||
RestorePhaseWaitingForPluginOperationsPartiallyFailed RestorePhase = "WaitingForPluginOperationsPartiallyFailed"
|
||||
|
||||
// RestorePhaseCompleted means the restore has run successfully
|
||||
// without errors.
|
||||
RestorePhaseCompleted RestorePhase = "Completed"
|
||||
@@ -301,6 +319,21 @@ type RestoreStatus struct {
|
||||
// +optional
|
||||
// +nullable
|
||||
Progress *RestoreProgress `json:"progress,omitempty"`
|
||||
|
||||
// RestoreItemOperationsAttempted is the total number of attempted
|
||||
// async RestoreItemAction operations for this restore.
|
||||
// +optional
|
||||
RestoreItemOperationsAttempted int `json:"restoreItemOperationsAttempted,omitempty"`
|
||||
|
||||
// RestoreItemOperationsCompleted is the total number of successfully completed
|
||||
// async RestoreItemAction operations for this restore.
|
||||
// +optional
|
||||
RestoreItemOperationsCompleted int `json:"restoreItemOperationsCompleted,omitempty"`
|
||||
|
||||
// RestoreItemOperationsFailed is the total number of async
|
||||
// RestoreItemAction operations for this restore which ended with an error.
|
||||
// +optional
|
||||
RestoreItemOperationsFailed int `json:"restoreItemOperationsFailed,omitempty"`
|
||||
}
|
||||
|
||||
// RestoreProgress stores information about the restore's execution progress
|
||||
@@ -317,6 +350,11 @@ type RestoreProgress struct {
|
||||
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:object:generate=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=restores,verbs=create;delete;get;list;patch;update;watch
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=restores/status,verbs=get;update;patch
|
||||
|
||||
// Restore is a Velero resource that represents the application of
|
||||
// resources from a Velero backup to a target Kubernetes cluster.
|
||||
@@ -299,6 +299,26 @@ func (in *BackupSpec) DeepCopyInto(out *BackupSpec) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.IncludedClusterScopedResources != nil {
|
||||
in, out := &in.IncludedClusterScopedResources, &out.IncludedClusterScopedResources
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ExcludedClusterScopedResources != nil {
|
||||
in, out := &in.ExcludedClusterScopedResources, &out.ExcludedClusterScopedResources
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.IncludedNamespaceScopedResources != nil {
|
||||
in, out := &in.IncludedNamespaceScopedResources, &out.IncludedNamespaceScopedResources
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ExcludedNamespaceScopedResources != nil {
|
||||
in, out := &in.ExcludedNamespaceScopedResources, &out.ExcludedNamespaceScopedResources
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.LabelSelector != nil {
|
||||
in, out := &in.LabelSelector, &out.LabelSelector
|
||||
*out = new(metav1.LabelSelector)
|
||||
@@ -350,6 +370,12 @@ func (in *BackupSpec) DeepCopyInto(out *BackupSpec) {
|
||||
}
|
||||
}
|
||||
out.CSISnapshotTimeout = in.CSISnapshotTimeout
|
||||
out.ItemOperationTimeout = in.ItemOperationTimeout
|
||||
if in.ResourcePolicy != nil {
|
||||
in, out := &in.ResourcePolicy, &out.ResourcePolicy
|
||||
*out = new(corev1.TypedLocalObjectReference)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSpec.
|
||||
@@ -1300,6 +1326,7 @@ func (in *RestoreSpec) DeepCopyInto(out *RestoreSpec) {
|
||||
**out = **in
|
||||
}
|
||||
in.Hooks.DeepCopyInto(&out.Hooks)
|
||||
out.ItemOperationTimeout = in.ItemOperationTimeout
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSpec.
|
||||
|
||||
@@ -28,11 +28,20 @@ import (
|
||||
|
||||
// GetItemFilePath returns an item's file path once extracted from a Velero backup archive.
|
||||
func GetItemFilePath(rootDir, groupResource, namespace, name string) string {
|
||||
switch namespace {
|
||||
case "":
|
||||
return filepath.Join(rootDir, velerov1api.ResourcesDir, groupResource, velerov1api.ClusterScopedDir, name+".json")
|
||||
default:
|
||||
return filepath.Join(rootDir, velerov1api.ResourcesDir, groupResource, velerov1api.NamespaceScopedDir, namespace, name+".json")
|
||||
return GetVersionedItemFilePath(rootDir, groupResource, namespace, name, "")
|
||||
}
|
||||
|
||||
// GetVersionedItemFilePath returns an item's file path once extracted from a Velero backup archive, with version included.
|
||||
func GetVersionedItemFilePath(rootDir, groupResource, namespace, name, versionPath string) string {
|
||||
return filepath.Join(rootDir, velerov1api.ResourcesDir, groupResource, versionPath, GetScopeDir(namespace), namespace, name+".json")
|
||||
}
|
||||
|
||||
// GetScopeDir returns NamespaceScopedDir if namespace is present, or ClusterScopedDir if empty
|
||||
func GetScopeDir(namespace string) string {
|
||||
if namespace == "" {
|
||||
return velerov1api.ClusterScopedDir
|
||||
} else {
|
||||
return velerov1api.NamespaceScopedDir
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,4 +28,22 @@ func TestGetItemFilePath(t *testing.T) {
|
||||
|
||||
res = GetItemFilePath("root", "resource", "namespace", "item")
|
||||
assert.Equal(t, "root/resources/resource/namespaces/namespace/item.json", res)
|
||||
|
||||
res = GetItemFilePath("", "resource", "", "item")
|
||||
assert.Equal(t, "resources/resource/cluster/item.json", res)
|
||||
|
||||
res = GetVersionedItemFilePath("root", "resource", "", "item", "")
|
||||
assert.Equal(t, "root/resources/resource/cluster/item.json", res)
|
||||
|
||||
res = GetVersionedItemFilePath("root", "resource", "namespace", "item", "")
|
||||
assert.Equal(t, "root/resources/resource/namespaces/namespace/item.json", res)
|
||||
|
||||
res = GetVersionedItemFilePath("root", "resource", "namespace", "item", "v1")
|
||||
assert.Equal(t, "root/resources/resource/v1/namespaces/namespace/item.json", res)
|
||||
|
||||
res = GetVersionedItemFilePath("root", "resource", "", "item", "v1")
|
||||
assert.Equal(t, "root/resources/resource/v1/cluster/item.json", res)
|
||||
|
||||
res = GetVersionedItemFilePath("", "resource", "", "item", "")
|
||||
assert.Equal(t, "resources/resource/cluster/item.json", res)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -34,22 +33,26 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/hook"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||
biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2"
|
||||
vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/podexec"
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
)
|
||||
|
||||
// BackupVersion is the current backup major version for Velero.
|
||||
@@ -63,15 +66,16 @@ const BackupFormatVersion = "1.1.0"
|
||||
type Backupper interface {
|
||||
// Backup takes a backup using the specification in the velerov1api.Backup and writes backup and log data
|
||||
// to the given writers.
|
||||
Backup(logger logrus.FieldLogger, backup *Request, backupFile io.Writer, actions []biav1.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error
|
||||
BackupWithResolvers(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer,
|
||||
backupItemActionResolver framework.BackupItemActionResolver, itemSnapshotterResolver framework.ItemSnapshotterResolver,
|
||||
volumeSnapshotterGetter VolumeSnapshotterGetter) error
|
||||
Backup(logger logrus.FieldLogger, backup *Request, backupFile io.Writer, actions []biav2.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error
|
||||
BackupWithResolvers(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, backupItemActionResolver framework.BackupItemActionResolverV2, volumeSnapshotterGetter VolumeSnapshotterGetter) error
|
||||
FinalizeBackup(log logrus.FieldLogger, backupRequest *Request, inBackupFile io.Reader, outBackupFile io.Writer,
|
||||
backupItemActionResolver framework.BackupItemActionResolverV2,
|
||||
asyncBIAOperations []*itemoperation.BackupOperation) error
|
||||
}
|
||||
|
||||
// kubernetesBackupper implements Backupper.
|
||||
type kubernetesBackupper struct {
|
||||
backupClient velerov1client.BackupsGetter
|
||||
kbClient kbclient.Client
|
||||
dynamicFactory client.DynamicFactory
|
||||
discoveryHelper discovery.Helper
|
||||
podCommandExecutor podexec.PodCommandExecutor
|
||||
@@ -98,7 +102,7 @@ func cohabitatingResources() map[string]*cohabitatingResource {
|
||||
|
||||
// NewKubernetesBackupper creates a new kubernetesBackupper.
|
||||
func NewKubernetesBackupper(
|
||||
backupClient velerov1client.BackupsGetter,
|
||||
kbClient kbclient.Client,
|
||||
discoveryHelper discovery.Helper,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
@@ -109,7 +113,7 @@ func NewKubernetesBackupper(
|
||||
uploaderType string,
|
||||
) (Backupper, error) {
|
||||
return &kubernetesBackupper{
|
||||
backupClient: backupClient,
|
||||
kbClient: kbClient,
|
||||
discoveryHelper: discoveryHelper,
|
||||
dynamicFactory: dynamicFactory,
|
||||
podCommandExecutor: podCommandExecutor,
|
||||
@@ -174,18 +178,15 @@ type VolumeSnapshotterGetter interface {
|
||||
// back up individual resources that don't prevent the backup from continuing to be processed) are logged
|
||||
// to the backup log.
|
||||
func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer,
|
||||
actions []biav1.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error {
|
||||
backupItemActions := framework.NewBackupItemActionResolver(actions)
|
||||
itemSnapshotters := framework.NewItemSnapshotterResolver(nil)
|
||||
return kb.BackupWithResolvers(log, backupRequest, backupFile, backupItemActions, itemSnapshotters,
|
||||
volumeSnapshotterGetter)
|
||||
actions []biav2.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error {
|
||||
backupItemActions := framework.NewBackupItemActionResolverV2(actions)
|
||||
return kb.BackupWithResolvers(log, backupRequest, backupFile, backupItemActions, volumeSnapshotterGetter)
|
||||
}
|
||||
|
||||
func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
||||
backupRequest *Request,
|
||||
backupFile io.Writer,
|
||||
backupItemActionResolver framework.BackupItemActionResolver,
|
||||
itemSnapshotterResolver framework.ItemSnapshotterResolver,
|
||||
backupItemActionResolver framework.BackupItemActionResolverV2,
|
||||
volumeSnapshotterGetter VolumeSnapshotterGetter) error {
|
||||
gzippedData := gzip.NewWriter(backupFile)
|
||||
defer gzippedData.Close()
|
||||
@@ -202,9 +203,22 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
||||
log.Infof("Including namespaces: %s", backupRequest.NamespaceIncludesExcludes.IncludesString())
|
||||
log.Infof("Excluding namespaces: %s", backupRequest.NamespaceIncludesExcludes.ExcludesString())
|
||||
|
||||
backupRequest.ResourceIncludesExcludes = collections.GetResourceIncludesExcludes(kb.discoveryHelper, backupRequest.Spec.IncludedResources, backupRequest.Spec.ExcludedResources)
|
||||
log.Infof("Including resources: %s", backupRequest.ResourceIncludesExcludes.IncludesString())
|
||||
log.Infof("Excluding resources: %s", backupRequest.ResourceIncludesExcludes.ExcludesString())
|
||||
if collections.UseOldResourceFilters(backupRequest.Spec) {
|
||||
backupRequest.ResourceIncludesExcludes = collections.GetGlobalResourceIncludesExcludes(kb.discoveryHelper, log,
|
||||
backupRequest.Spec.IncludedResources,
|
||||
backupRequest.Spec.ExcludedResources,
|
||||
backupRequest.Spec.IncludeClusterResources,
|
||||
*backupRequest.NamespaceIncludesExcludes)
|
||||
} else {
|
||||
backupRequest.ResourceIncludesExcludes = collections.GetScopeResourceIncludesExcludes(kb.discoveryHelper, log,
|
||||
backupRequest.Spec.IncludedNamespaceScopedResources,
|
||||
backupRequest.Spec.ExcludedNamespaceScopedResources,
|
||||
backupRequest.Spec.IncludedClusterScopedResources,
|
||||
backupRequest.Spec.ExcludedClusterScopedResources,
|
||||
*backupRequest.NamespaceIncludesExcludes,
|
||||
)
|
||||
}
|
||||
|
||||
log.Infof("Backing up all volumes using pod volume backup: %t", boolptr.IsSetToTrue(backupRequest.Backup.Spec.DefaultVolumesToFsBackup))
|
||||
|
||||
var err error
|
||||
@@ -220,12 +234,6 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
||||
return err
|
||||
}
|
||||
|
||||
backupRequest.ResolvedItemSnapshotters, err = itemSnapshotterResolver.ResolveActions(kb.discoveryHelper, log)
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Debugf("Error from itemSnapshotterResolver.ResolveActions")
|
||||
return err
|
||||
}
|
||||
|
||||
backupRequest.BackedUpItems = map[itemKey]struct{}{}
|
||||
|
||||
podVolumeTimeout := kb.podVolumeTimeout
|
||||
@@ -252,7 +260,7 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
||||
|
||||
// set up a temp dir for the itemCollector to use to temporarily
|
||||
// store items as they're scraped from the API.
|
||||
tempDir, err := ioutil.TempDir("", "")
|
||||
tempDir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating temp dir for backup")
|
||||
}
|
||||
@@ -271,16 +279,22 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
||||
items := collector.getAllItems()
|
||||
log.WithField("progress", "").Infof("Collected %d items matching the backup spec from the Kubernetes API (actual number of items backed up may be more or less depending on velero.io/exclude-from-backup annotation, plugins returning additional related items to back up, etc.)", len(items))
|
||||
|
||||
backupRequest.Status.Progress = &velerov1api.BackupProgress{TotalItems: len(items)}
|
||||
patch := fmt.Sprintf(`{"status":{"progress":{"totalItems":%d}}}`, len(items))
|
||||
if _, err := kb.backupClient.Backups(backupRequest.Namespace).Patch(context.TODO(), backupRequest.Name, types.MergePatchType, []byte(patch), metav1.PatchOptions{}); err != nil {
|
||||
updated := backupRequest.Backup.DeepCopy()
|
||||
if updated.Status.Progress == nil {
|
||||
updated.Status.Progress = &velerov1api.BackupProgress{}
|
||||
}
|
||||
|
||||
updated.Status.Progress.TotalItems = len(items)
|
||||
if err := kube.PatchResource(backupRequest.Backup, updated, kb.kbClient); err != nil {
|
||||
log.WithError(errors.WithStack((err))).Warn("Got error trying to update backup's status.progress.totalItems")
|
||||
}
|
||||
backupRequest.Status.Progress = &velerov1api.BackupProgress{TotalItems: len(items)}
|
||||
|
||||
itemBackupper := &itemBackupper{
|
||||
backupRequest: backupRequest,
|
||||
tarWriter: tw,
|
||||
dynamicFactory: kb.dynamicFactory,
|
||||
kbClient: kb.kbClient,
|
||||
discoveryHelper: kb.discoveryHelper,
|
||||
podVolumeBackupper: podVolumeBackupper,
|
||||
podVolumeSnapshotTracker: newPVCSnapshotTracker(),
|
||||
@@ -323,13 +337,16 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
||||
lastUpdate = &val
|
||||
case <-ticker.C:
|
||||
if lastUpdate != nil {
|
||||
backupRequest.Status.Progress.TotalItems = lastUpdate.totalItems
|
||||
backupRequest.Status.Progress.ItemsBackedUp = lastUpdate.itemsBackedUp
|
||||
|
||||
patch := fmt.Sprintf(`{"status":{"progress":{"totalItems":%d,"itemsBackedUp":%d}}}`, lastUpdate.totalItems, lastUpdate.itemsBackedUp)
|
||||
if _, err := kb.backupClient.Backups(backupRequest.Namespace).Patch(context.TODO(), backupRequest.Name, types.MergePatchType, []byte(patch), metav1.PatchOptions{}); err != nil {
|
||||
updated := backupRequest.Backup.DeepCopy()
|
||||
if updated.Status.Progress == nil {
|
||||
updated.Status.Progress = &velerov1api.BackupProgress{}
|
||||
}
|
||||
updated.Status.Progress.TotalItems = lastUpdate.totalItems
|
||||
updated.Status.Progress.ItemsBackedUp = lastUpdate.itemsBackedUp
|
||||
if err := kube.PatchResource(backupRequest.Backup, updated, kb.kbClient); err != nil {
|
||||
log.WithError(errors.WithStack((err))).Warn("Got error trying to update backup's status.progress")
|
||||
}
|
||||
backupRequest.Status.Progress = &velerov1api.BackupProgress{TotalItems: lastUpdate.totalItems, ItemsBackedUp: lastUpdate.itemsBackedUp}
|
||||
lastUpdate = nil
|
||||
}
|
||||
}
|
||||
@@ -391,10 +408,12 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
||||
// no more progress updates will be sent on the 'update' channel
|
||||
quit <- struct{}{}
|
||||
|
||||
// back up CRD for resource if found. We should only need to do this if we've backed up at least
|
||||
// one item for the resource and IncludeClusterResources is nil. If IncludeClusterResources is false
|
||||
// we don't want to back it up, and if it's true it will already be included.
|
||||
if backupRequest.Spec.IncludeClusterResources == nil {
|
||||
// back up CRD(this is a CRD definition of the resource, it's a CRD instance) for resource if found.
|
||||
// We should only need to do this if we've backed up at least one item for the resource
|
||||
// and the CRD type(this is the CRD type itself) is neither included or excluded.
|
||||
// When it's included, the resource's CRD is already handled. When it's excluded, no need to check.
|
||||
if !backupRequest.ResourceIncludesExcludes.ShouldExclude(kuberesource.CustomResourceDefinitions.String()) &&
|
||||
!backupRequest.ResourceIncludesExcludes.ShouldInclude(kuberesource.CustomResourceDefinitions.String()) {
|
||||
for gr := range backedUpGroupResources {
|
||||
kb.backupCRD(log, gr, itemBackupper)
|
||||
}
|
||||
@@ -402,13 +421,17 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
||||
|
||||
// do a final update on progress since we may have just added some CRDs and may not have updated
|
||||
// for the last few processed items.
|
||||
backupRequest.Status.Progress.TotalItems = len(backupRequest.BackedUpItems)
|
||||
backupRequest.Status.Progress.ItemsBackedUp = len(backupRequest.BackedUpItems)
|
||||
updated = backupRequest.Backup.DeepCopy()
|
||||
if updated.Status.Progress == nil {
|
||||
updated.Status.Progress = &velerov1api.BackupProgress{}
|
||||
}
|
||||
updated.Status.Progress.TotalItems = len(backupRequest.BackedUpItems)
|
||||
updated.Status.Progress.ItemsBackedUp = len(backupRequest.BackedUpItems)
|
||||
|
||||
patch = fmt.Sprintf(`{"status":{"progress":{"totalItems":%d,"itemsBackedUp":%d}}}`, len(backupRequest.BackedUpItems), len(backupRequest.BackedUpItems))
|
||||
if _, err := kb.backupClient.Backups(backupRequest.Namespace).Patch(context.TODO(), backupRequest.Name, types.MergePatchType, []byte(patch), metav1.PatchOptions{}); err != nil {
|
||||
if err := kube.PatchResource(backupRequest.Backup, updated, kb.kbClient); err != nil {
|
||||
log.WithError(errors.WithStack((err))).Warn("Got error trying to update backup's status.progress")
|
||||
}
|
||||
backupRequest.Status.Progress = &velerov1api.BackupProgress{TotalItems: len(backupRequest.BackedUpItems), ItemsBackedUp: len(backupRequest.BackedUpItems)}
|
||||
|
||||
log.WithField("progress", "").Infof("Backed up a total of %d items", len(backupRequest.BackedUpItems))
|
||||
|
||||
@@ -416,7 +439,7 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
||||
}
|
||||
|
||||
func (kb *kubernetesBackupper) backupItem(log logrus.FieldLogger, gr schema.GroupResource, itemBackupper *itemBackupper, unstructured *unstructured.Unstructured, preferredGVR schema.GroupVersionResource) bool {
|
||||
backedUpItem, err := itemBackupper.backupItem(log, unstructured, gr, preferredGVR, false)
|
||||
backedUpItem, _, err := itemBackupper.backupItem(log, unstructured, gr, preferredGVR, false, false)
|
||||
if aggregate, ok := err.(kubeerrs.Aggregate); ok {
|
||||
log.WithField("name", unstructured.GetName()).Infof("%d errors encountered backup up item", len(aggregate.Errors()))
|
||||
// log each error separately so we get error location info in the log, and an
|
||||
@@ -434,6 +457,25 @@ func (kb *kubernetesBackupper) backupItem(log logrus.FieldLogger, gr schema.Grou
|
||||
return backedUpItem
|
||||
}
|
||||
|
||||
func (kb *kubernetesBackupper) finalizeItem(log logrus.FieldLogger, gr schema.GroupResource, itemBackupper *itemBackupper, unstructured *unstructured.Unstructured, preferredGVR schema.GroupVersionResource) (bool, []FileForArchive) {
|
||||
backedUpItem, updateFiles, err := itemBackupper.backupItem(log, unstructured, gr, preferredGVR, true, true)
|
||||
if aggregate, ok := err.(kubeerrs.Aggregate); ok {
|
||||
log.WithField("name", unstructured.GetName()).Infof("%d errors encountered backup up item", len(aggregate.Errors()))
|
||||
// log each error separately so we get error location info in the log, and an
|
||||
// accurate count of errors
|
||||
for _, err = range aggregate.Errors() {
|
||||
log.WithError(err).WithField("name", unstructured.GetName()).Error("Error backing up item")
|
||||
}
|
||||
|
||||
return false, updateFiles
|
||||
}
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("name", unstructured.GetName()).Error("Error backing up item")
|
||||
return false, updateFiles
|
||||
}
|
||||
return backedUpItem, updateFiles
|
||||
}
|
||||
|
||||
// backupCRD checks if the resource is a custom resource, and if so, backs up the custom resource definition
|
||||
// associated with it.
|
||||
func (kb *kubernetesBackupper) backupCRD(log logrus.FieldLogger, gr schema.GroupResource, itemBackupper *itemBackupper) {
|
||||
@@ -467,6 +509,7 @@ func (kb *kubernetesBackupper) backupCRD(log logrus.FieldLogger, gr schema.Group
|
||||
log.WithError(errors.WithStack(err)).Errorf("Error getting CRD %s", gr.String())
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("Found associated CRD %s to add to backup", gr.String())
|
||||
|
||||
kb.backupItem(log, gvr.GroupResource(), itemBackupper, unstructured, gvr)
|
||||
@@ -492,6 +535,181 @@ func (kb *kubernetesBackupper) writeBackupVersion(tw *tar.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kb *kubernetesBackupper) FinalizeBackup(log logrus.FieldLogger,
|
||||
backupRequest *Request,
|
||||
inBackupFile io.Reader,
|
||||
outBackupFile io.Writer,
|
||||
backupItemActionResolver framework.BackupItemActionResolverV2,
|
||||
asyncBIAOperations []*itemoperation.BackupOperation) error {
|
||||
|
||||
gzw := gzip.NewWriter(outBackupFile)
|
||||
defer gzw.Close()
|
||||
tw := tar.NewWriter(gzw)
|
||||
defer tw.Close()
|
||||
|
||||
gzr, err := gzip.NewReader(inBackupFile)
|
||||
if err != nil {
|
||||
log.Infof("error creating gzip reader: %v", err)
|
||||
return err
|
||||
}
|
||||
defer gzr.Close()
|
||||
tr := tar.NewReader(gzr)
|
||||
|
||||
backupRequest.ResolvedActions, err = backupItemActionResolver.ResolveActions(kb.discoveryHelper, log)
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Debugf("Error from backupItemActionResolver.ResolveActions")
|
||||
return err
|
||||
}
|
||||
|
||||
backupRequest.BackedUpItems = map[itemKey]struct{}{}
|
||||
|
||||
// set up a temp dir for the itemCollector to use to temporarily
|
||||
// store items as they're scraped from the API.
|
||||
tempDir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating temp dir for backup")
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
collector := &itemCollector{
|
||||
log: log,
|
||||
backupRequest: backupRequest,
|
||||
discoveryHelper: kb.discoveryHelper,
|
||||
dynamicFactory: kb.dynamicFactory,
|
||||
cohabitatingResources: cohabitatingResources(),
|
||||
dir: tempDir,
|
||||
pageSize: kb.clientPageSize,
|
||||
}
|
||||
|
||||
// Get item list from itemoperation.BackupOperation.Spec.PostOperationItems
|
||||
var resourceIDs []velero.ResourceIdentifier
|
||||
for _, operation := range asyncBIAOperations {
|
||||
if len(operation.Spec.PostOperationItems) != 0 {
|
||||
resourceIDs = append(resourceIDs, operation.Spec.PostOperationItems...)
|
||||
}
|
||||
}
|
||||
items := collector.getItemsFromResourceIdentifiers(resourceIDs)
|
||||
log.WithField("progress", "").Infof("Collected %d items from the async BIA operations PostOperationItems list", len(items))
|
||||
|
||||
itemBackupper := &itemBackupper{
|
||||
backupRequest: backupRequest,
|
||||
tarWriter: tw,
|
||||
dynamicFactory: kb.dynamicFactory,
|
||||
kbClient: kb.kbClient,
|
||||
discoveryHelper: kb.discoveryHelper,
|
||||
itemHookHandler: &hook.NoOpItemHookHandler{},
|
||||
}
|
||||
updateFiles := make(map[string]FileForArchive)
|
||||
backedUpGroupResources := map[schema.GroupResource]bool{}
|
||||
totalItems := len(items)
|
||||
|
||||
for i, item := range items {
|
||||
log.WithFields(map[string]interface{}{
|
||||
"progress": "",
|
||||
"resource": item.groupResource.String(),
|
||||
"namespace": item.namespace,
|
||||
"name": item.name,
|
||||
}).Infof("Processing item")
|
||||
|
||||
// use an anonymous func so we can defer-close/remove the file
|
||||
// as soon as we're done with it
|
||||
func() {
|
||||
var unstructured unstructured.Unstructured
|
||||
|
||||
f, err := os.Open(item.path)
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("Error opening file containing item")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if err := json.NewDecoder(f).Decode(&unstructured); err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("Error decoding JSON from file")
|
||||
return
|
||||
}
|
||||
|
||||
backedUp, itemFiles := kb.finalizeItem(log, item.groupResource, itemBackupper, &unstructured, item.preferredGVR)
|
||||
if backedUp {
|
||||
backedUpGroupResources[item.groupResource] = true
|
||||
for _, itemFile := range itemFiles {
|
||||
updateFiles[itemFile.FilePath] = itemFile
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// updated total is computed as "how many items we've backed up so far, plus
|
||||
// how many items we know of that are remaining"
|
||||
totalItems = len(backupRequest.BackedUpItems) + (len(items) - (i + 1))
|
||||
|
||||
log.WithFields(map[string]interface{}{
|
||||
"progress": "",
|
||||
"resource": item.groupResource.String(),
|
||||
"namespace": item.namespace,
|
||||
"name": item.name,
|
||||
}).Infof("Updated %d items out of an estimated total of %d (estimate will change throughout the backup finalizer)", len(backupRequest.BackedUpItems), totalItems)
|
||||
}
|
||||
|
||||
// write new tar archive replacing files in original with content updateFiles for matches
|
||||
buildFinalTarball(tr, tw, updateFiles)
|
||||
log.WithField("progress", "").Infof("Updated a total of %d items", len(backupRequest.BackedUpItems))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildFinalTarball(tr *tar.Reader, tw *tar.Writer, updateFiles map[string]FileForArchive) error {
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
newFile, ok := updateFiles[header.Name]
|
||||
if ok {
|
||||
// add updated file to archive, skip over tr file content
|
||||
if err := tw.WriteHeader(newFile.Header); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if _, err := tw.Write(newFile.FileBytes); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
delete(updateFiles, header.Name)
|
||||
// skip over file contents from old tarball
|
||||
_, err := io.ReadAll(tr)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
} else {
|
||||
// Add original content to new tarball, as item wasn't updated
|
||||
oldContents, err := io.ReadAll(tr)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := tw.WriteHeader(header); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if _, err := tw.Write(oldContents); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// iterate over any remaining map entries, which represent updated items that
|
||||
// were not in the original backup tarball
|
||||
for _, newFile := range updateFiles {
|
||||
if err := tw.WriteHeader(newFile.Header); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if _, err := tw.Write(newFile.FileBytes); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
type tarWriter interface {
|
||||
io.Closer
|
||||
Write([]byte) (int, error)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,9 +18,9 @@ package backup
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -37,12 +37,18 @@ import (
|
||||
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
kbClient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/hook"
|
||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/archive"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||
"github.com/vmware-tanzu/velero/pkg/features"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
@@ -59,6 +65,7 @@ type itemBackupper struct {
|
||||
backupRequest *Request
|
||||
tarWriter tarWriter
|
||||
dynamicFactory client.DynamicFactory
|
||||
kbClient kbClient.Client
|
||||
discoveryHelper discovery.Helper
|
||||
podVolumeBackupper podvolume.Backupper
|
||||
podVolumeSnapshotTracker *pvcSnapshotTracker
|
||||
@@ -68,52 +75,87 @@ type itemBackupper struct {
|
||||
snapshotLocationVolumeSnapshotters map[string]vsv1.VolumeSnapshotter
|
||||
}
|
||||
|
||||
type FileForArchive struct {
|
||||
FilePath string
|
||||
Header *tar.Header
|
||||
FileBytes []byte
|
||||
}
|
||||
|
||||
// backupItem backs up an individual item to tarWriter. The item may be excluded based on the
|
||||
// namespaces IncludesExcludes list.
|
||||
// If finalize is true, then it returns the bytes instead of writing them to the tarWriter
|
||||
// In addition to the error return, backupItem also returns a bool indicating whether the item
|
||||
// was actually backed up.
|
||||
func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstructured, groupResource schema.GroupResource, preferredGVR schema.GroupVersionResource, mustInclude bool) (bool, error) {
|
||||
func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstructured, groupResource schema.GroupResource, preferredGVR schema.GroupVersionResource, mustInclude, finalize bool) (bool, []FileForArchive, error) {
|
||||
selectedForBackup, files, err := ib.backupItemInternal(logger, obj, groupResource, preferredGVR, mustInclude, finalize)
|
||||
// return if not selected, an error occurred, there are no files to add, or for finalize
|
||||
if selectedForBackup == false || err != nil || len(files) == 0 || finalize {
|
||||
return selectedForBackup, files, err
|
||||
}
|
||||
for _, file := range files {
|
||||
if err := ib.tarWriter.WriteHeader(file.Header); err != nil {
|
||||
return false, []FileForArchive{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if _, err := ib.tarWriter.Write(file.FileBytes); err != nil {
|
||||
return false, []FileForArchive{}, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return true, []FileForArchive{}, nil
|
||||
}
|
||||
|
||||
func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runtime.Unstructured, groupResource schema.GroupResource, preferredGVR schema.GroupVersionResource, mustInclude, finalize bool) (bool, []FileForArchive, error) {
|
||||
var itemFiles []FileForArchive
|
||||
metadata, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, itemFiles, err
|
||||
}
|
||||
|
||||
namespace := metadata.GetNamespace()
|
||||
name := metadata.GetName()
|
||||
|
||||
log := logger.WithField("name", name)
|
||||
log = log.WithField("resource", groupResource.String())
|
||||
log = log.WithField("namespace", namespace)
|
||||
log := logger.WithFields(map[string]interface{}{
|
||||
"name": name,
|
||||
"resource": groupResource.String(),
|
||||
"namespace": namespace,
|
||||
})
|
||||
|
||||
if mustInclude {
|
||||
log.Infof("Skipping the exclusion checks for this resource")
|
||||
} else {
|
||||
if metadata.GetLabels()[excludeFromBackupLabel] == "true" {
|
||||
log.Infof("Excluding item because it has label %s=true", excludeFromBackupLabel)
|
||||
return false, nil
|
||||
return false, itemFiles, nil
|
||||
}
|
||||
// NOTE: we have to re-check namespace & resource includes/excludes because it's possible that
|
||||
// backupItem can be invoked by a custom action.
|
||||
if namespace != "" && !ib.backupRequest.NamespaceIncludesExcludes.ShouldInclude(namespace) {
|
||||
log.Info("Excluding item because namespace is excluded")
|
||||
return false, nil
|
||||
}
|
||||
// NOTE: we specifically allow namespaces to be backed up even if IncludeClusterResources is
|
||||
// false.
|
||||
if namespace == "" && groupResource != kuberesource.Namespaces && ib.backupRequest.Spec.IncludeClusterResources != nil && !*ib.backupRequest.Spec.IncludeClusterResources {
|
||||
log.Info("Excluding item because resource is cluster-scoped and backup.spec.includeClusterResources is false")
|
||||
return false, nil
|
||||
return false, itemFiles, nil
|
||||
}
|
||||
|
||||
if !ib.backupRequest.ResourceIncludesExcludes.ShouldInclude(groupResource.String()) {
|
||||
log.Info("Excluding item because resource is excluded")
|
||||
return false, nil
|
||||
// NOTE: we specifically allow namespaces to be backed up even if it's excluded.
|
||||
// This check is more permissive for cluster resources to let those passed in by
|
||||
// plugins' additional items to get involved.
|
||||
// Only expel cluster resource when it's specifically listed in the excluded list here.
|
||||
if namespace == "" && groupResource != kuberesource.Namespaces &&
|
||||
ib.backupRequest.ResourceIncludesExcludes.ShouldExclude(groupResource.String()) {
|
||||
log.Info("Excluding item because resource is cluster-scoped and is excluded by cluster filter.")
|
||||
return false, itemFiles, nil
|
||||
}
|
||||
|
||||
// Only check namespace-scoped resource to avoid expelling cluster resources
|
||||
// are not specified in included list.
|
||||
if namespace != "" && !ib.backupRequest.ResourceIncludesExcludes.ShouldInclude(groupResource.String()) {
|
||||
log.Info("Excluding item because resource is excluded")
|
||||
return false, itemFiles, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if metadata.GetDeletionTimestamp() != nil {
|
||||
log.Info("Skipping item because it's being deleted.")
|
||||
return false, nil
|
||||
return false, itemFiles, nil
|
||||
}
|
||||
|
||||
key := itemKey{
|
||||
@@ -125,23 +167,22 @@ func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstr
|
||||
if _, exists := ib.backupRequest.BackedUpItems[key]; exists {
|
||||
log.Info("Skipping item because it's already been backed up.")
|
||||
// returning true since this item *is* in the backup, even though we're not backing it up here
|
||||
return true, nil
|
||||
return true, itemFiles, nil
|
||||
}
|
||||
ib.backupRequest.BackedUpItems[key] = struct{}{}
|
||||
|
||||
log.Info("Backing up item")
|
||||
|
||||
log.Debug("Executing pre hooks")
|
||||
if err := ib.itemHookHandler.HandleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hook.PhasePre); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var (
|
||||
backupErrs []error
|
||||
pod *corev1api.Pod
|
||||
pvbVolumes []string
|
||||
)
|
||||
|
||||
log.Debug("Executing pre hooks")
|
||||
if err := ib.itemHookHandler.HandleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hook.PhasePre); err != nil {
|
||||
return false, itemFiles, err
|
||||
}
|
||||
|
||||
if groupResource == kuberesource.Pods {
|
||||
// pod needs to be initialized for the unstructured converter
|
||||
pod = new(corev1api.Pod)
|
||||
@@ -154,31 +195,28 @@ func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstr
|
||||
// any volumes that use a PVC that we've already backed up (this would be in a read-write-many scenario,
|
||||
// where it's been backed up from another pod), since we don't need >1 backup per PVC.
|
||||
for _, volume := range podvolume.GetVolumesByPod(pod, boolptr.IsSetToTrue(ib.backupRequest.Spec.DefaultVolumesToFsBackup)) {
|
||||
if found, pvcName := ib.podVolumeSnapshotTracker.HasPVCForPodVolume(pod, volume); found {
|
||||
// track the volumes that are PVCs using the PVC snapshot tracker, so that when we backup PVCs/PVs
|
||||
// via an item action in the next step, we don't snapshot PVs that will have their data backed up
|
||||
// with pod volume backup.
|
||||
ib.podVolumeSnapshotTracker.Track(pod, volume)
|
||||
|
||||
if found, pvcName := ib.podVolumeSnapshotTracker.TakenForPodVolume(pod, volume); found {
|
||||
log.WithFields(map[string]interface{}{
|
||||
"podVolume": volume,
|
||||
"pvcName": pvcName,
|
||||
}).Info("Pod volume uses a persistent volume claim which has already been backed up from another pod, skipping.")
|
||||
continue
|
||||
}
|
||||
|
||||
pvbVolumes = append(pvbVolumes, volume)
|
||||
}
|
||||
|
||||
// track the volumes that are PVCs using the PVC snapshot tracker, so that when we backup PVCs/PVs
|
||||
// via an item action in the next step, we don't snapshot PVs that will have their data backed up
|
||||
// with pod volume backup.
|
||||
ib.podVolumeSnapshotTracker.Track(pod, pvbVolumes)
|
||||
}
|
||||
}
|
||||
|
||||
// capture the version of the object before invoking plugin actions as the plugin may update
|
||||
// the group version of the object.
|
||||
// group version of this object
|
||||
// Used on filepath to backup up all groups and versions
|
||||
version := resourceVersion(obj)
|
||||
versionPath := resourceVersion(obj)
|
||||
|
||||
updatedObj, err := ib.executeActions(log, obj, groupResource, name, namespace, metadata)
|
||||
updatedObj, additionalItemFiles, err := ib.executeActions(log, obj, groupResource, name, namespace, metadata, finalize)
|
||||
if err != nil {
|
||||
backupErrs = append(backupErrs, err)
|
||||
|
||||
@@ -187,12 +225,13 @@ func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstr
|
||||
if err := ib.itemHookHandler.HandleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hook.PhasePost); err != nil {
|
||||
backupErrs = append(backupErrs, err)
|
||||
}
|
||||
|
||||
return false, kubeerrs.NewAggregate(backupErrs)
|
||||
return false, itemFiles, kubeerrs.NewAggregate(backupErrs)
|
||||
}
|
||||
|
||||
itemFiles = append(itemFiles, additionalItemFiles...)
|
||||
obj = updatedObj
|
||||
if metadata, err = meta.Accessor(obj); err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
return false, itemFiles, errors.WithStack(err)
|
||||
}
|
||||
// update name and namespace in case they were modified in an action
|
||||
name = metadata.GetName()
|
||||
@@ -211,6 +250,11 @@ func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstr
|
||||
|
||||
ib.backupRequest.PodVolumeBackups = append(ib.backupRequest.PodVolumeBackups, podVolumeBackups...)
|
||||
backupErrs = append(backupErrs, errs...)
|
||||
|
||||
// Mark the volumes that has been processed by pod volume backup as Taken in the tracker.
|
||||
for _, pvb := range podVolumeBackups {
|
||||
ib.podVolumeSnapshotTracker.Take(pod, pvb.Spec.Volume)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("Executing post hooks")
|
||||
@@ -219,33 +263,27 @@ func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstr
|
||||
}
|
||||
|
||||
if len(backupErrs) != 0 {
|
||||
return false, kubeerrs.NewAggregate(backupErrs)
|
||||
}
|
||||
|
||||
// Getting the preferred group version of this resource
|
||||
preferredVersion := preferredGVR.Version
|
||||
|
||||
var filePath string
|
||||
|
||||
// API Group version is now part of path of backup as a subdirectory
|
||||
// it will add a prefix to subdirectory name for the preferred version
|
||||
versionPath := version
|
||||
|
||||
if version == preferredVersion {
|
||||
versionPath = version + velerov1api.PreferredVersionDir
|
||||
}
|
||||
|
||||
if namespace != "" {
|
||||
filePath = filepath.Join(velerov1api.ResourcesDir, groupResource.String(), versionPath, velerov1api.NamespaceScopedDir, namespace, name+".json")
|
||||
} else {
|
||||
filePath = filepath.Join(velerov1api.ResourcesDir, groupResource.String(), versionPath, velerov1api.ClusterScopedDir, name+".json")
|
||||
return false, itemFiles, kubeerrs.NewAggregate(backupErrs)
|
||||
}
|
||||
|
||||
itemBytes, err := json.Marshal(obj.UnstructuredContent())
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
return false, itemFiles, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if versionPath == preferredGVR.Version {
|
||||
// backing up preferred version backup without API Group version - for backward compatibility
|
||||
log.Debugf("Resource %s/%s, version= %s, preferredVersion=%s", groupResource.String(), name, versionPath, preferredGVR.Version)
|
||||
itemFiles = append(itemFiles, getFileForArchive(namespace, name, groupResource.String(), "", itemBytes))
|
||||
versionPath = versionPath + velerov1api.PreferredVersionDir
|
||||
}
|
||||
|
||||
itemFiles = append(itemFiles, getFileForArchive(namespace, name, groupResource.String(), versionPath, itemBytes))
|
||||
return true, itemFiles, nil
|
||||
}
|
||||
|
||||
func getFileForArchive(namespace, name, groupResource, versionPath string, itemBytes []byte) FileForArchive {
|
||||
filePath := archive.GetVersionedItemFilePath("", groupResource, namespace, name, versionPath)
|
||||
hdr := &tar.Header{
|
||||
Name: filePath,
|
||||
Size: int64(len(itemBytes)),
|
||||
@@ -253,43 +291,7 @@ func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstr
|
||||
Mode: 0755,
|
||||
ModTime: time.Now(),
|
||||
}
|
||||
|
||||
if err := ib.tarWriter.WriteHeader(hdr); err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if _, err := ib.tarWriter.Write(itemBytes); err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// backing up the preferred version backup without API Group version on path - this is for backward compatibility
|
||||
|
||||
log.Debugf("Resource %s/%s, version= %s, preferredVersion=%s", groupResource.String(), name, version, preferredVersion)
|
||||
if version == preferredVersion {
|
||||
if namespace != "" {
|
||||
filePath = filepath.Join(velerov1api.ResourcesDir, groupResource.String(), velerov1api.NamespaceScopedDir, namespace, name+".json")
|
||||
} else {
|
||||
filePath = filepath.Join(velerov1api.ResourcesDir, groupResource.String(), velerov1api.ClusterScopedDir, name+".json")
|
||||
}
|
||||
|
||||
hdr = &tar.Header{
|
||||
Name: filePath,
|
||||
Size: int64(len(itemBytes)),
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: 0755,
|
||||
ModTime: time.Now(),
|
||||
}
|
||||
|
||||
if err := ib.tarWriter.WriteHeader(hdr); err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if _, err := ib.tarWriter.Write(itemBytes); err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return FileForArchive{FilePath: filePath, Header: hdr, FileBytes: itemBytes}
|
||||
}
|
||||
|
||||
// backupPodVolumes triggers pod volume backups of the specified pod volumes, and returns a list of PodVolumeBackups
|
||||
@@ -304,7 +306,7 @@ func (ib *itemBackupper) backupPodVolumes(log logrus.FieldLogger, pod *corev1api
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return ib.podVolumeBackupper.BackupPodVolumes(ib.backupRequest.Backup, pod, volumes, log)
|
||||
return ib.podVolumeBackupper.BackupPodVolumes(ib.backupRequest.Backup, pod, volumes, ib.backupRequest.ResPolicies, log)
|
||||
}
|
||||
|
||||
func (ib *itemBackupper) executeActions(
|
||||
@@ -313,29 +315,76 @@ func (ib *itemBackupper) executeActions(
|
||||
groupResource schema.GroupResource,
|
||||
name, namespace string,
|
||||
metadata metav1.Object,
|
||||
) (runtime.Unstructured, error) {
|
||||
finalize bool,
|
||||
) (runtime.Unstructured, []FileForArchive, error) {
|
||||
var itemFiles []FileForArchive
|
||||
for _, action := range ib.backupRequest.ResolvedActions {
|
||||
if !action.ShouldUse(groupResource, namespace, metadata, log) {
|
||||
continue
|
||||
}
|
||||
log.Info("Executing custom action")
|
||||
|
||||
updatedItem, additionalItemIdentifiers, err := action.Execute(obj, ib.backupRequest.Backup)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error executing custom action (groupResource=%s, namespace=%s, name=%s)", groupResource.String(), namespace, name)
|
||||
actionName := action.Name()
|
||||
if act, err := ib.getMatchAction(obj, groupResource, actionName); err != nil {
|
||||
return nil, itemFiles, errors.WithStack(err)
|
||||
} else if act != nil && act.Type == resourcepolicies.Skip {
|
||||
log.Infof("Skip executing Backup Item Action: %s of resource %s: %s/%s for the matched resource policies", actionName, groupResource, namespace, name)
|
||||
continue
|
||||
}
|
||||
|
||||
updatedItem, additionalItemIdentifiers, operationID, postOperationItems, err := action.Execute(obj, ib.backupRequest.Backup)
|
||||
|
||||
if err != nil {
|
||||
return nil, itemFiles, errors.Wrapf(err, "error executing custom action (groupResource=%s, namespace=%s, name=%s)", groupResource.String(), namespace, name)
|
||||
}
|
||||
|
||||
u := &unstructured.Unstructured{Object: updatedItem.UnstructuredContent()}
|
||||
mustInclude := u.GetAnnotations()[mustIncludeAdditionalItemAnnotation] == "true"
|
||||
mustInclude := u.GetAnnotations()[mustIncludeAdditionalItemAnnotation] == "true" || finalize
|
||||
// remove the annotation as it's for communication between BIA and velero server,
|
||||
// we don't want the resource be restored with this annotation.
|
||||
if _, ok := u.GetAnnotations()[mustIncludeAdditionalItemAnnotation]; ok {
|
||||
delete(u.GetAnnotations(), mustIncludeAdditionalItemAnnotation)
|
||||
}
|
||||
obj = u
|
||||
|
||||
// If async plugin started async operation, add it to the ItemOperations list
|
||||
// ignore during finalize phase
|
||||
if operationID != "" {
|
||||
if finalize {
|
||||
return nil, itemFiles, errors.New(fmt.Sprintf("Backup Item Action created operation during finalize (groupResource=%s, namespace=%s, name=%s)", groupResource.String(), namespace, name))
|
||||
}
|
||||
resourceIdentifier := velero.ResourceIdentifier{
|
||||
GroupResource: groupResource,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
}
|
||||
now := metav1.Now()
|
||||
newOperation := itemoperation.BackupOperation{
|
||||
Spec: itemoperation.BackupOperationSpec{
|
||||
BackupName: ib.backupRequest.Backup.Name,
|
||||
BackupUID: string(ib.backupRequest.Backup.UID),
|
||||
BackupItemAction: action.Name(),
|
||||
ResourceIdentifier: resourceIdentifier,
|
||||
OperationID: operationID,
|
||||
},
|
||||
Status: itemoperation.OperationStatus{
|
||||
Phase: itemoperation.OperationPhaseNew,
|
||||
Created: &now,
|
||||
},
|
||||
}
|
||||
newOperation.Spec.PostOperationItems = postOperationItems
|
||||
itemOperList := ib.backupRequest.GetItemOperationsList()
|
||||
*itemOperList = append(*itemOperList, &newOperation)
|
||||
}
|
||||
|
||||
for _, additionalItem := range additionalItemIdentifiers {
|
||||
gvr, resource, err := ib.discoveryHelper.ResourceFor(additionalItem.GroupResource.WithVersion(""))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, itemFiles, err
|
||||
}
|
||||
|
||||
client, err := ib.dynamicFactory.ClientForGroupVersionResource(gvr.GroupVersion(), resource, additionalItem.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, itemFiles, err
|
||||
}
|
||||
|
||||
item, err := client.Get(additionalItem.Name, metav1.GetOptions{})
|
||||
@@ -349,21 +398,17 @@ func (ib *itemBackupper) executeActions(
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
return nil, itemFiles, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if _, err = ib.backupItem(log, item, gvr.GroupResource(), gvr, mustInclude); err != nil {
|
||||
return nil, err
|
||||
_, additionalItemFiles, err := ib.backupItem(log, item, gvr.GroupResource(), gvr, mustInclude, finalize)
|
||||
if err != nil {
|
||||
return nil, itemFiles, err
|
||||
}
|
||||
itemFiles = append(itemFiles, additionalItemFiles...)
|
||||
}
|
||||
// remove the annotation as it's for communication between BIA and velero server,
|
||||
// we don't want the resource be restored with this annotation.
|
||||
if _, ok := u.GetAnnotations()[mustIncludeAdditionalItemAnnotation]; ok {
|
||||
delete(u.GetAnnotations(), mustIncludeAdditionalItemAnnotation)
|
||||
}
|
||||
obj = u
|
||||
}
|
||||
return obj, nil
|
||||
return obj, itemFiles, nil
|
||||
}
|
||||
|
||||
// volumeSnapshotter instantiates and initializes a VolumeSnapshotter given a VolumeSnapshotLocation,
|
||||
@@ -403,6 +448,10 @@ const (
|
||||
azureCsiZoneKey = "topology.disk.csi.azure.com/zone"
|
||||
gkeCsiZoneKey = "topology.gke.io/zone"
|
||||
gkeZoneSeparator = "__"
|
||||
|
||||
// OpenStack CSI drivers topology keys
|
||||
cinderCsiZoneKey = "topology.manila.csi.openstack.org/zone"
|
||||
manilaCsiZoneKey = "topology.cinder.csi.openstack.org/zone"
|
||||
)
|
||||
|
||||
// takePVSnapshot triggers a snapshot for the volume/disk underlying a PersistentVolume if the provided
|
||||
@@ -438,6 +487,16 @@ func (ib *itemBackupper) takePVSnapshot(obj runtime.Unstructured, log logrus.Fie
|
||||
return nil
|
||||
}
|
||||
|
||||
if ib.backupRequest.ResPolicies != nil {
|
||||
if action, err := ib.backupRequest.ResPolicies.GetMatchAction(pv); err != nil {
|
||||
log.WithError(err).Errorf("Error getting matched resource policies for pv %s", pv.Name)
|
||||
return nil
|
||||
} else if action != nil && action.Type == resourcepolicies.Skip {
|
||||
log.Infof("skip snapshot of pv %s for the matched resource policies", pv.Name)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: -- once failure-domain.beta.kubernetes.io/zone is no longer
|
||||
// supported in any velero-supported version of Kubernetes, remove fallback checking of it
|
||||
pvFailureDomainZone, labelFound := pv.Labels[zoneLabel]
|
||||
@@ -447,7 +506,7 @@ func (ib *itemBackupper) takePVSnapshot(obj runtime.Unstructured, log logrus.Fie
|
||||
if !labelFound {
|
||||
var k string
|
||||
log.Infof("label %q is not present on PersistentVolume", zoneLabelDeprecated)
|
||||
k, pvFailureDomainZone = zoneFromPVNodeAffinity(pv, awsEbsCsiZoneKey, azureCsiZoneKey, gkeCsiZoneKey, zoneLabel, zoneLabelDeprecated)
|
||||
k, pvFailureDomainZone = zoneFromPVNodeAffinity(pv, awsEbsCsiZoneKey, azureCsiZoneKey, gkeCsiZoneKey, cinderCsiZoneKey, manilaCsiZoneKey, zoneLabel, zoneLabelDeprecated)
|
||||
if pvFailureDomainZone != "" {
|
||||
log.Infof("zone info from nodeAffinity requirements: %s, key: %s", pvFailureDomainZone, k)
|
||||
} else {
|
||||
@@ -475,7 +534,7 @@ func (ib *itemBackupper) takePVSnapshot(obj runtime.Unstructured, log logrus.Fie
|
||||
continue
|
||||
}
|
||||
if volumeID == "" {
|
||||
log.Infof("No volume ID returned by volume snapshotter for persistent volume")
|
||||
log.Warn("No volume ID returned by volume snapshotter for persistent volume")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -524,6 +583,27 @@ func (ib *itemBackupper) takePVSnapshot(obj runtime.Unstructured, log logrus.Fie
|
||||
return kubeerrs.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (ib *itemBackupper) getMatchAction(obj runtime.Unstructured, groupResource schema.GroupResource, backupItemActionName string) (*resourcepolicies.Action, error) {
|
||||
if ib.backupRequest.ResPolicies != nil && groupResource == kuberesource.PersistentVolumeClaims && (backupItemActionName == "velero.io/csi-pvc-backupper" || backupItemActionName == "velero.io/vsphere-pvc-backupper") {
|
||||
pvc := corev1api.PersistentVolumeClaim{}
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &pvc); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
pvName := pvc.Spec.VolumeName
|
||||
if pvName == "" {
|
||||
return nil, errors.Errorf("PVC has no volume backing this claim")
|
||||
}
|
||||
|
||||
pv := &corev1api.PersistentVolume{}
|
||||
if err := ib.kbClient.Get(context.Background(), kbClient.ObjectKey{Name: pvName}, pv); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return ib.backupRequest.ResPolicies.GetMatchAction(pv)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func volumeSnapshot(backup *velerov1api.Backup, volumeName, volumeID, volumeType, az, location string, iops *int64) *volume.Snapshot {
|
||||
return &volume.Snapshot{
|
||||
Spec: volume.SnapshotSpec{
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
)
|
||||
|
||||
@@ -58,11 +59,34 @@ type kubernetesResource struct {
|
||||
namespace, name, path string
|
||||
}
|
||||
|
||||
// getItemsFromResourceIdentifiers converts ResourceIdentifiers to
|
||||
// kubernetesResources
|
||||
func (r *itemCollector) getItemsFromResourceIdentifiers(resourceIDs []velero.ResourceIdentifier) []*kubernetesResource {
|
||||
|
||||
grResourceIDsMap := make(map[schema.GroupResource][]velero.ResourceIdentifier)
|
||||
for _, resourceID := range resourceIDs {
|
||||
grResourceIDsMap[resourceID.GroupResource] = append(grResourceIDsMap[resourceID.GroupResource], resourceID)
|
||||
}
|
||||
return r.getItems(grResourceIDsMap)
|
||||
}
|
||||
|
||||
// getAllItems gets all relevant items from all API groups.
|
||||
func (r *itemCollector) getAllItems() []*kubernetesResource {
|
||||
return r.getItems(nil)
|
||||
}
|
||||
|
||||
// getItems gets all relevant items from all API groups.
|
||||
// If resourceIDsMap is nil, then all items from the cluster are
|
||||
// pulled for each API group, subject to include/exclude rules.
|
||||
// If resourceIDsMap is supplied, then only those resources are
|
||||
// returned, with the appropriate APIGroup information filled in. In
|
||||
// this case, include/exclude rules are not invoked, since we already
|
||||
// have the list of items, we just need the item collector/discovery
|
||||
// helper to fill in the missing GVR, etc. context.
|
||||
func (r *itemCollector) getItems(resourceIDsMap map[schema.GroupResource][]velero.ResourceIdentifier) []*kubernetesResource {
|
||||
var resources []*kubernetesResource
|
||||
for _, group := range r.discoveryHelper.Resources() {
|
||||
groupItems, err := r.getGroupItems(r.log, group)
|
||||
groupItems, err := r.getGroupItems(r.log, group, resourceIDsMap)
|
||||
if err != nil {
|
||||
r.log.WithError(err).WithField("apiGroup", group.String()).Error("Error collecting resources from API group")
|
||||
continue
|
||||
@@ -75,7 +99,9 @@ func (r *itemCollector) getAllItems() []*kubernetesResource {
|
||||
}
|
||||
|
||||
// getGroupItems collects all relevant items from a single API group.
|
||||
func (r *itemCollector) getGroupItems(log logrus.FieldLogger, group *metav1.APIResourceList) ([]*kubernetesResource, error) {
|
||||
// If resourceIDsMap is supplied, then only those items are returned,
|
||||
// with GVR/APIResource metadata supplied.
|
||||
func (r *itemCollector) getGroupItems(log logrus.FieldLogger, group *metav1.APIResourceList, resourceIDsMap map[schema.GroupResource][]velero.ResourceIdentifier) ([]*kubernetesResource, error) {
|
||||
log = log.WithField("group", group.GroupVersion)
|
||||
|
||||
log.Infof("Getting items for group")
|
||||
@@ -93,7 +119,7 @@ func (r *itemCollector) getGroupItems(log logrus.FieldLogger, group *metav1.APIR
|
||||
|
||||
var items []*kubernetesResource
|
||||
for _, resource := range group.APIResources {
|
||||
resourceItems, err := r.getResourceItems(log, gv, resource)
|
||||
resourceItems, err := r.getResourceItems(log, gv, resource, resourceIDsMap)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("resource", resource.String()).Error("Error getting items for resource")
|
||||
continue
|
||||
@@ -164,7 +190,9 @@ func getOrderedResourcesForType(orderedResources map[string]string, resourceType
|
||||
}
|
||||
|
||||
// getResourceItems collects all relevant items for a given group-version-resource.
|
||||
func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.GroupVersion, resource metav1.APIResource) ([]*kubernetesResource, error) {
|
||||
// If resourceIDsMap is supplied, the items will be pulled from here
|
||||
// rather than from the cluster and applying include/exclude rules.
|
||||
func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.GroupVersion, resource metav1.APIResource, resourceIDsMap map[schema.GroupResource][]velero.ResourceIdentifier) ([]*kubernetesResource, error) {
|
||||
log = log.WithField("resource", resource.Name)
|
||||
|
||||
log.Info("Getting items for resource")
|
||||
@@ -182,25 +210,44 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// If the resource we are backing up is NOT namespaces, and it is cluster-scoped, check to see if
|
||||
// we should include it based on the IncludeClusterResources setting.
|
||||
if gr != kuberesource.Namespaces && clusterScoped {
|
||||
if r.backupRequest.Spec.IncludeClusterResources == nil {
|
||||
if !r.backupRequest.NamespaceIncludesExcludes.IncludeEverything() {
|
||||
// when IncludeClusterResources == nil (auto), only directly
|
||||
// back up cluster-scoped resources if we're doing a full-cluster
|
||||
// (all namespaces) backup. Note that in the case of a subset of
|
||||
// namespaces being backed up, some related cluster-scoped resources
|
||||
// may still be backed up if triggered by a custom action (e.g. PVC->PV).
|
||||
// If we're processing namespaces themselves, we will not skip here, they may be
|
||||
// filtered out later.
|
||||
log.Info("Skipping resource because it's cluster-scoped and only specific namespaces are included in the backup")
|
||||
return nil, nil
|
||||
}
|
||||
} else if !*r.backupRequest.Spec.IncludeClusterResources {
|
||||
log.Info("Skipping resource because it's cluster-scoped")
|
||||
// If we have a resourceIDs map, then only return items listed in it
|
||||
if resourceIDsMap != nil {
|
||||
resourceIDs, ok := resourceIDsMap[gr]
|
||||
if !ok {
|
||||
log.Info("Skipping resource because no items found in supplied ResourceIdentifier list")
|
||||
return nil, nil
|
||||
}
|
||||
var items []*kubernetesResource
|
||||
for _, resourceID := range resourceIDs {
|
||||
log.WithFields(
|
||||
logrus.Fields{
|
||||
"namespace": resourceID.Namespace,
|
||||
"name": resourceID.Name,
|
||||
},
|
||||
).Infof("Getting item")
|
||||
resourceClient, err := r.dynamicFactory.ClientForGroupVersionResource(gv, resource, resourceID.Namespace)
|
||||
unstructured, err := resourceClient.Get(resourceID.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("Error getting item")
|
||||
continue
|
||||
}
|
||||
|
||||
path, err := r.writeToFile(unstructured)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Error writing item to file")
|
||||
continue
|
||||
}
|
||||
|
||||
items = append(items, &kubernetesResource{
|
||||
groupResource: gr,
|
||||
preferredGVR: preferredGVR,
|
||||
namespace: resourceID.Namespace,
|
||||
name: resourceID.Name,
|
||||
path: path,
|
||||
})
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
if !r.backupRequest.ResourceIncludesExcludes.ShouldInclude(gr.String()) {
|
||||
@@ -226,7 +273,7 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
|
||||
namespacesToList := getNamespacesToList(r.backupRequest.NamespaceIncludesExcludes)
|
||||
|
||||
// Check if we're backing up namespaces for a less-than-full backup.
|
||||
// We enter this block if resource is Namespaces and the namespae list is either empty or contains
|
||||
// We enter this block if resource is Namespaces and the namespace list is either empty or contains
|
||||
// an explicit namespace list. (We skip this block if the list contains "" since that indicates
|
||||
// a full-cluster backup
|
||||
if gr == kuberesource.Namespaces && (len(namespacesToList) == 0 || namespacesToList[0] != "") {
|
||||
@@ -368,7 +415,7 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
|
||||
}
|
||||
|
||||
func (r *itemCollector) writeToFile(item *unstructured.Unstructured) (string, error) {
|
||||
f, err := ioutil.TempFile(r.dir, "")
|
||||
f, err := os.CreateTemp(r.dir, "")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error creating temp file")
|
||||
}
|
||||
|
||||
@@ -20,45 +20,60 @@ import (
|
||||
"fmt"
|
||||
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// pvcSnapshotTracker keeps track of persistent volume claims that have been snapshotted
|
||||
// with pod volume backup.
|
||||
type pvcSnapshotTracker struct {
|
||||
pvcs sets.String
|
||||
pvcs map[string]pvcSnapshotStatus
|
||||
}
|
||||
|
||||
type pvcSnapshotStatus struct {
|
||||
taken bool
|
||||
}
|
||||
|
||||
func newPVCSnapshotTracker() *pvcSnapshotTracker {
|
||||
return &pvcSnapshotTracker{
|
||||
pvcs: sets.NewString(),
|
||||
pvcs: make(map[string]pvcSnapshotStatus),
|
||||
}
|
||||
}
|
||||
|
||||
// Track takes a pod and a list of volumes from that pod that were snapshotted, and
|
||||
// tracks each snapshotted volume that's a PVC.
|
||||
func (t *pvcSnapshotTracker) Track(pod *corev1api.Pod, snapshottedVolumes []string) {
|
||||
for _, volumeName := range snapshottedVolumes {
|
||||
// if the volume is a PVC, track it
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
if volume.Name == volumeName {
|
||||
if volume.PersistentVolumeClaim != nil {
|
||||
t.pvcs.Insert(key(pod.Namespace, volume.PersistentVolumeClaim.ClaimName))
|
||||
// Track indicates a volume from a pod should be snapshotted by pod volume backup.
|
||||
func (t *pvcSnapshotTracker) Track(pod *corev1api.Pod, volumeName string) {
|
||||
// if the volume is a PVC, track it
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
if volume.Name == volumeName {
|
||||
if volume.PersistentVolumeClaim != nil {
|
||||
if _, ok := t.pvcs[key(pod.Namespace, volume.PersistentVolumeClaim.ClaimName)]; !ok {
|
||||
t.pvcs[key(pod.Namespace, volume.PersistentVolumeClaim.ClaimName)] = pvcSnapshotStatus{false}
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Take indicates a volume from a pod has been taken by pod volume backup.
|
||||
func (t *pvcSnapshotTracker) Take(pod *corev1api.Pod, volumeName string) {
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
if volume.Name == volumeName {
|
||||
if volume.PersistentVolumeClaim != nil {
|
||||
t.pvcs[key(pod.Namespace, volume.PersistentVolumeClaim.ClaimName)] = pvcSnapshotStatus{true}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Has returns true if the PVC with the specified namespace and name has been tracked.
|
||||
func (t *pvcSnapshotTracker) Has(namespace, name string) bool {
|
||||
return t.pvcs.Has(key(namespace, name))
|
||||
_, found := t.pvcs[key(namespace, name)]
|
||||
return found
|
||||
}
|
||||
|
||||
// HasPVCForPodVolume returns true and the PVC's name if the pod volume with the specified name uses a
|
||||
// PVC and that PVC has been tracked.
|
||||
func (t *pvcSnapshotTracker) HasPVCForPodVolume(pod *corev1api.Pod, volume string) (bool, string) {
|
||||
// TakenForPodVolume returns true and the PVC's name if the pod volume with the specified name uses a
|
||||
// PVC and that PVC has been taken by pod volume backup.
|
||||
func (t *pvcSnapshotTracker) TakenForPodVolume(pod *corev1api.Pod, volume string) (bool, string) {
|
||||
for _, podVolume := range pod.Spec.Volumes {
|
||||
if podVolume.Name != volume {
|
||||
continue
|
||||
@@ -68,7 +83,12 @@ func (t *pvcSnapshotTracker) HasPVCForPodVolume(pod *corev1api.Pod, volume strin
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if !t.pvcs.Has(key(pod.Namespace, podVolume.PersistentVolumeClaim.ClaimName)) {
|
||||
status, found := t.pvcs[key(pod.Namespace, podVolume.PersistentVolumeClaim.ClaimName)]
|
||||
if !found {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if !status.taken {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -174,20 +174,20 @@ func TestRemapCRDVersionActionData(t *testing.T) {
|
||||
t.Run(tName, func(t *testing.T) {
|
||||
// We don't need a Go struct of the v1 data, just an unstructured to pass into the plugin.
|
||||
v1File := fmt.Sprintf("testdata/v1/%s.json", test.crd)
|
||||
f, err := ioutil.ReadFile(v1File)
|
||||
f, err := os.ReadFile(v1File)
|
||||
require.NoError(t, err)
|
||||
|
||||
var obj unstructured.Unstructured
|
||||
err = json.Unmarshal([]byte(f), &obj)
|
||||
err = json.Unmarshal(f, &obj)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load a v1beta1 struct into the beta client to be returned
|
||||
v1beta1File := fmt.Sprintf("testdata/v1beta1/%s.json", test.crd)
|
||||
f, err = ioutil.ReadFile(v1beta1File)
|
||||
f, err = os.ReadFile(v1beta1File)
|
||||
require.NoError(t, err)
|
||||
|
||||
var crd apiextv1beta1.CustomResourceDefinition
|
||||
err = json.Unmarshal([]byte(f), &crd)
|
||||
err = json.Unmarshal(f, &crd)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = betaClient.Create(context.TODO(), &crd, metav1.CreateOptions{})
|
||||
|
||||
@@ -23,7 +23,9 @@ import (
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/hook"
|
||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
@@ -43,14 +45,24 @@ type Request struct {
|
||||
StorageLocation *velerov1api.BackupStorageLocation
|
||||
SnapshotLocations []*velerov1api.VolumeSnapshotLocation
|
||||
NamespaceIncludesExcludes *collections.IncludesExcludes
|
||||
ResourceIncludesExcludes *collections.IncludesExcludes
|
||||
ResourceIncludesExcludes collections.IncludesExcludesInterface
|
||||
ResourceHooks []hook.ResourceHook
|
||||
ResolvedActions []framework.BackupItemResolvedAction
|
||||
ResolvedItemSnapshotters []framework.ItemSnapshotterResolvedAction
|
||||
ResolvedActions []framework.BackupItemResolvedActionV2
|
||||
VolumeSnapshots []*volume.Snapshot
|
||||
PodVolumeBackups []*velerov1api.PodVolumeBackup
|
||||
BackedUpItems map[itemKey]struct{}
|
||||
CSISnapshots []snapshotv1api.VolumeSnapshot
|
||||
itemOperationsList *[]*itemoperation.BackupOperation
|
||||
ResPolicies *resourcepolicies.Policies
|
||||
}
|
||||
|
||||
// GetItemOperationsList returns ItemOperationsList, initializing it if necessary
|
||||
func (r *Request) GetItemOperationsList() *[]*itemoperation.BackupOperation {
|
||||
if r.itemOperationsList == nil {
|
||||
list := []*itemoperation.BackupOperation{}
|
||||
r.itemOperationsList = &list
|
||||
}
|
||||
return r.itemOperationsList
|
||||
}
|
||||
|
||||
// BackupResourceList returns the list of backed up resources grouped by the API
|
||||
|
||||
@@ -20,8 +20,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -123,6 +125,10 @@ func (b *BackupBuilder) FromSchedule(schedule *velerov1api.Schedule) *BackupBuil
|
||||
})
|
||||
}
|
||||
|
||||
if schedule.Spec.Template.ResourcePolicy != nil {
|
||||
b.ResourcePolicies(schedule.Spec.Template.ResourcePolicy.Name)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -150,6 +156,30 @@ func (b *BackupBuilder) ExcludedResources(resources ...string) *BackupBuilder {
|
||||
return b
|
||||
}
|
||||
|
||||
// IncludedClusterScopedResources sets the Backup's included cluster resources.
|
||||
func (b *BackupBuilder) IncludedClusterScopedResources(resources ...string) *BackupBuilder {
|
||||
b.object.Spec.IncludedClusterScopedResources = resources
|
||||
return b
|
||||
}
|
||||
|
||||
// ExcludedClusterScopedResources sets the Backup's excluded cluster resources.
|
||||
func (b *BackupBuilder) ExcludedClusterScopedResources(resources ...string) *BackupBuilder {
|
||||
b.object.Spec.ExcludedClusterScopedResources = resources
|
||||
return b
|
||||
}
|
||||
|
||||
// IncludedNamespaceScopedResources sets the Backup's included namespaced resources.
|
||||
func (b *BackupBuilder) IncludedNamespaceScopedResources(resources ...string) *BackupBuilder {
|
||||
b.object.Spec.IncludedNamespaceScopedResources = resources
|
||||
return b
|
||||
}
|
||||
|
||||
// ExcludedNamespaceScopedResources sets the Backup's excluded namespaced resources.
|
||||
func (b *BackupBuilder) ExcludedNamespaceScopedResources(resources ...string) *BackupBuilder {
|
||||
b.object.Spec.ExcludedNamespaceScopedResources = resources
|
||||
return b
|
||||
}
|
||||
|
||||
// IncludeClusterResources sets the Backup's "include cluster resources" flag.
|
||||
func (b *BackupBuilder) IncludeClusterResources(val bool) *BackupBuilder {
|
||||
b.object.Spec.IncludeClusterResources = &val
|
||||
@@ -245,3 +275,15 @@ func (b *BackupBuilder) CSISnapshotTimeout(timeout time.Duration) *BackupBuilder
|
||||
b.object.Spec.CSISnapshotTimeout.Duration = timeout
|
||||
return b
|
||||
}
|
||||
|
||||
// ItemOperationTimeout sets the Backup's ItemOperationTimeout
|
||||
func (b *BackupBuilder) ItemOperationTimeout(timeout time.Duration) *BackupBuilder {
|
||||
b.object.Spec.ItemOperationTimeout.Duration = timeout
|
||||
return b
|
||||
}
|
||||
|
||||
// ResourcePolicies sets the Backup's resource polices.
|
||||
func (b *BackupBuilder) ResourcePolicies(name string) *BackupBuilder {
|
||||
b.object.Spec.ResourcePolicy = &v1.TypedLocalObjectReference{Kind: resourcepolicies.ConfigmapRefType, Name: name}
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ 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
|
||||
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,
|
||||
|
||||
@@ -5,7 +5,7 @@ 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
|
||||
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,
|
||||
|
||||
@@ -146,3 +146,10 @@ func WithGenerateName(val string) func(obj metav1.Object) {
|
||||
obj.SetGenerateName(val)
|
||||
}
|
||||
}
|
||||
|
||||
// WithManagedFields is a functional option that applies the specified managed fields to an object.
|
||||
func WithManagedFields(val []metav1.ManagedFieldsEntry) func(obj metav1.Object) {
|
||||
return func(obj metav1.Object) {
|
||||
obj.SetManagedFields(val)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,3 +165,9 @@ func (b *RestoreBuilder) CompletionTimestamp(val time.Time) *RestoreBuilder {
|
||||
b.object.Status.CompletionTimestamp = &metav1.Time{Time: val}
|
||||
return b
|
||||
}
|
||||
|
||||
// ItemOperationTimeout sets the Restore's ItemOperationTimeout
|
||||
func (b *RestoreBuilder) ItemOperationTimeout(timeout time.Duration) *RestoreBuilder {
|
||||
b.object.Spec.ItemOperationTimeout.Duration = timeout
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ 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
|
||||
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,
|
||||
|
||||
@@ -27,20 +27,15 @@ import (
|
||||
"github.com/vmware-tanzu/velero/pkg/buildinfo"
|
||||
)
|
||||
|
||||
func buildConfigFromFlags(context, kubeconfigPath string, precedence []string) (*rest.Config, error) {
|
||||
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath, Precedence: precedence},
|
||||
&clientcmd.ConfigOverrides{
|
||||
CurrentContext: context,
|
||||
}).ClientConfig()
|
||||
}
|
||||
|
||||
// Config returns a *rest.Config, using either the kubeconfig (if specified) or an in-cluster
|
||||
// configuration.
|
||||
func Config(kubeconfig, kubecontext, baseName string, qps float32, burst int) (*rest.Config, error) {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.ExplicitPath = kubeconfig
|
||||
clientConfig, err := buildConfigFromFlags(kubecontext, kubeconfig, loadingRules.Precedence)
|
||||
configOverrides := &clientcmd.ConfigOverrides{CurrentContext: kubecontext}
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
|
||||
clientConfig, err := kubeConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error finding Kubernetes API server config in --kubeconfig, $KUBECONFIG, or in-cluster configuration")
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ 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
|
||||
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,
|
||||
|
||||
@@ -77,11 +77,10 @@ type factory struct {
|
||||
}
|
||||
|
||||
// NewFactory returns a Factory.
|
||||
func NewFactory(baseName, kubecontext string, config VeleroConfig) Factory {
|
||||
func NewFactory(baseName string, config VeleroConfig) Factory {
|
||||
f := &factory{
|
||||
flags: pflag.NewFlagSet("", pflag.ContinueOnError),
|
||||
baseName: baseName,
|
||||
kubecontext: kubecontext,
|
||||
flags: pflag.NewFlagSet("", pflag.ContinueOnError),
|
||||
baseName: baseName,
|
||||
}
|
||||
|
||||
f.namespace = os.Getenv("VELERO_NAMESPACE")
|
||||
@@ -97,7 +96,7 @@ func NewFactory(baseName, kubecontext string, config VeleroConfig) Factory {
|
||||
|
||||
f.flags.StringVar(&f.kubeconfig, "kubeconfig", "", "Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration")
|
||||
f.flags.StringVarP(&f.namespace, "namespace", "n", f.namespace, "The namespace in which Velero should operate")
|
||||
//f.flags.StringVar(&f.kubecontext, "kubecontext", "", "The context to use to talk to the Kubernetes apiserver. If unset defaults to whatever your current-context is (kubectl config current-context)")
|
||||
f.flags.StringVar(&f.kubecontext, "kubecontext", "", "The context to use to talk to the Kubernetes apiserver. If unset defaults to whatever your current-context is (kubectl config current-context)")
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -128,6 +127,7 @@ func (f *factory) KubeClient() (kubernetes.Interface, error) {
|
||||
return nil, err
|
||||
}
|
||||
kubeClient, err := kubernetes.NewForConfig(clientConfig)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ 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
|
||||
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,
|
||||
@@ -31,14 +31,14 @@ func TestFactory(t *testing.T) {
|
||||
|
||||
// Env variable should set the namespace if no config or argument are used
|
||||
os.Setenv("VELERO_NAMESPACE", "env-velero")
|
||||
f := NewFactory("velero", "", make(map[string]interface{}))
|
||||
f := NewFactory("velero", make(map[string]interface{}))
|
||||
|
||||
assert.Equal(t, "env-velero", f.Namespace())
|
||||
|
||||
os.Unsetenv("VELERO_NAMESPACE")
|
||||
|
||||
// Argument should change the namespace
|
||||
f = NewFactory("velero", "", make(map[string]interface{}))
|
||||
f = NewFactory("velero", make(map[string]interface{}))
|
||||
s := "flag-velero"
|
||||
flags := new(pflag.FlagSet)
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestFactory(t *testing.T) {
|
||||
|
||||
// An argument overrides the env variable if both are set.
|
||||
os.Setenv("VELERO_NAMESPACE", "env-velero")
|
||||
f = NewFactory("velero", "", make(map[string]interface{}))
|
||||
f = NewFactory("velero", make(map[string]interface{}))
|
||||
flags = new(pflag.FlagSet)
|
||||
|
||||
f.BindFlags(flags)
|
||||
|
||||
@@ -82,25 +82,30 @@ func NewCreateCommand(f client.Factory, use string) *cobra.Command {
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
Name string
|
||||
TTL time.Duration
|
||||
SnapshotVolumes flag.OptionalBool
|
||||
DefaultVolumesToFsBackup flag.OptionalBool
|
||||
IncludeNamespaces flag.StringArray
|
||||
ExcludeNamespaces flag.StringArray
|
||||
IncludeResources flag.StringArray
|
||||
ExcludeResources flag.StringArray
|
||||
Labels flag.Map
|
||||
Selector flag.LabelSelector
|
||||
IncludeClusterResources flag.OptionalBool
|
||||
Wait bool
|
||||
StorageLocation string
|
||||
SnapshotLocations []string
|
||||
FromSchedule string
|
||||
OrderedResources string
|
||||
CSISnapshotTimeout time.Duration
|
||||
|
||||
client veleroclient.Interface
|
||||
Name string
|
||||
TTL time.Duration
|
||||
SnapshotVolumes flag.OptionalBool
|
||||
DefaultVolumesToFsBackup flag.OptionalBool
|
||||
IncludeNamespaces flag.StringArray
|
||||
ExcludeNamespaces flag.StringArray
|
||||
IncludeResources flag.StringArray
|
||||
ExcludeResources flag.StringArray
|
||||
IncludeClusterScopedResources flag.StringArray
|
||||
ExcludeClusterScopedResources flag.StringArray
|
||||
IncludeNamespaceScopedResources flag.StringArray
|
||||
ExcludeNamespaceScopedResources flag.StringArray
|
||||
Labels flag.Map
|
||||
Selector flag.LabelSelector
|
||||
IncludeClusterResources flag.OptionalBool
|
||||
Wait bool
|
||||
StorageLocation string
|
||||
SnapshotLocations []string
|
||||
FromSchedule string
|
||||
OrderedResources string
|
||||
CSISnapshotTimeout time.Duration
|
||||
ItemOperationTimeout time.Duration
|
||||
ResPoliciesConfigmap string
|
||||
client veleroclient.Interface
|
||||
}
|
||||
|
||||
func NewCreateOptions() *CreateOptions {
|
||||
@@ -116,24 +121,31 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
|
||||
flags.DurationVar(&o.TTL, "ttl", o.TTL, "How long before the backup can be garbage collected.")
|
||||
flags.Var(&o.IncludeNamespaces, "include-namespaces", "Namespaces to include in the backup (use '*' for all namespaces).")
|
||||
flags.Var(&o.ExcludeNamespaces, "exclude-namespaces", "Namespaces to exclude from the backup.")
|
||||
flags.Var(&o.IncludeResources, "include-resources", "Resources to include in the backup, formatted as resource.group, such as storageclasses.storage.k8s.io (use '*' for all resources).")
|
||||
flags.Var(&o.ExcludeResources, "exclude-resources", "Resources to exclude from the backup, formatted as resource.group, such as storageclasses.storage.k8s.io.")
|
||||
flags.Var(&o.IncludeResources, "include-resources", "Resources to include in the backup, formatted as resource.group, such as storageclasses.storage.k8s.io (use '*' for all resources). Cannot work with include-cluster-scoped-resources, exclude-cluster-scoped-resources, include-namespace-scoped-resources and exclude-namespace-scoped-resources.")
|
||||
flags.Var(&o.ExcludeResources, "exclude-resources", "Resources to exclude from the backup, formatted as resource.group, such as storageclasses.storage.k8s.io. Cannot work with include-cluster-scoped-resources, exclude-cluster-scoped-resources, include-namespace-scoped-resources and exclude-namespace-scoped-resources.")
|
||||
flags.Var(&o.IncludeClusterScopedResources, "include-cluster-scoped-resources", "Cluster-scoped resources to include in the backup, formatted as resource.group, such as storageclasses.storage.k8s.io(use '*' for all resources). Cannot work with include-resources, exclude-resources and include-cluster-resources.")
|
||||
flags.Var(&o.ExcludeClusterScopedResources, "exclude-cluster-scoped-resources", "Cluster-scoped resources to exclude from the backup, formatted as resource.group, such as storageclasses.storage.k8s.io(use '*' for all resources). Cannot work with include-resources, exclude-resources and include-cluster-resources.")
|
||||
flags.Var(&o.IncludeNamespaceScopedResources, "include-namespace-scoped-resources", "Namespaced resources to include in the backup, formatted as resource.group, such as deployments.apps(use '*' for all resources). Cannot work with include-resources, exclude-resources and include-cluster-resources.")
|
||||
flags.Var(&o.ExcludeNamespaceScopedResources, "exclude-namespace-scoped-resources", "Namespaced resources to exclude from the backup, formatted as resource.group, such as deployments.apps(use '*' for all resources). Cannot work with include-resources, exclude-resources and include-cluster-resources.")
|
||||
flags.Var(&o.Labels, "labels", "Labels to apply to the backup.")
|
||||
flags.StringVar(&o.StorageLocation, "storage-location", "", "Location in which to store the backup.")
|
||||
flags.StringSliceVar(&o.SnapshotLocations, "volume-snapshot-locations", o.SnapshotLocations, "List of locations (at most one per provider) where volume snapshots should be stored.")
|
||||
flags.VarP(&o.Selector, "selector", "l", "Only back up resources matching this label selector.")
|
||||
flags.StringVar(&o.OrderedResources, "ordered-resources", "", "Mapping Kinds to an ordered list of specific resources of that Kind. Resource names are separated by commas and their names are in format 'namespace/resourcename'. For cluster scope resource, simply use resource name. Key-value pairs in the mapping are separated by semi-colon. Example: 'pods=ns1/pod1,ns1/pod2;persistentvolumeclaims=ns1/pvc4,ns1/pvc8'. Optional.")
|
||||
flags.DurationVar(&o.CSISnapshotTimeout, "csi-snapshot-timeout", o.CSISnapshotTimeout, "How long to wait for CSI snapshot creation before timeout.")
|
||||
flags.DurationVar(&o.ItemOperationTimeout, "item-operation-timeout", o.ItemOperationTimeout, "How long to wait for async plugin operations before timeout.")
|
||||
f := flags.VarPF(&o.SnapshotVolumes, "snapshot-volumes", "", "Take snapshots of PersistentVolumes as part of the backup. If the parameter is not set, it is treated as setting to 'true'.")
|
||||
// this allows the user to just specify "--snapshot-volumes" as shorthand for "--snapshot-volumes=true"
|
||||
// like a normal bool flag
|
||||
f.NoOptDefVal = "true"
|
||||
|
||||
f = flags.VarPF(&o.IncludeClusterResources, "include-cluster-resources", "", "Include cluster-scoped resources in the backup")
|
||||
f = flags.VarPF(&o.IncludeClusterResources, "include-cluster-resources", "", "Include cluster-scoped resources in the backup. Cannot work with include-cluster-scoped-resources, exclude-cluster-scoped-resources, include-namespace-scoped-resources and exclude-namespace-scoped-resources.")
|
||||
f.NoOptDefVal = "true"
|
||||
|
||||
f = flags.VarPF(&o.DefaultVolumesToFsBackup, "default-volumes-to-fs-backup", "", "Use pod volume file system backup by default for volumes")
|
||||
f.NoOptDefVal = "true"
|
||||
|
||||
flags.StringVar(&o.ResPoliciesConfigmap, "resource-policies-configmap", "", "Reference to the resource policies configmap that backup using")
|
||||
}
|
||||
|
||||
// BindWait binds the wait flag separately so it is not called by other create
|
||||
@@ -160,7 +172,7 @@ func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Facto
|
||||
|
||||
// Ensure that unless FromSchedule is set, args contains a backup name
|
||||
if o.FromSchedule == "" && len(args) != 1 {
|
||||
return fmt.Errorf("A backup name is required, unless you are creating based on a schedule.")
|
||||
return fmt.Errorf("a backup name is required, unless you are creating based on a schedule")
|
||||
}
|
||||
|
||||
errs := collections.ValidateNamespaceIncludesExcludes(o.IncludeNamespaces, o.ExcludeNamespaces)
|
||||
@@ -168,6 +180,12 @@ func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Facto
|
||||
return kubeerrs.NewAggregate(errs)
|
||||
}
|
||||
|
||||
if o.oldAndNewFilterParametersUsedTogether() {
|
||||
return fmt.Errorf("include-resources, exclude-resources and include-cluster-resources are old filter parameters.\n" +
|
||||
"include-cluster-scoped-resources, exclude-cluster-scoped-resources, include-namespace-scoped-resources and exclude-namespace-scoped-resources are new filter parameters.\n" +
|
||||
"They cannot be used together")
|
||||
}
|
||||
|
||||
if o.StorageLocation != "" {
|
||||
location := &velerov1api.BackupStorageLocation{}
|
||||
if err := client.Get(context.Background(), kbclient.ObjectKey{
|
||||
@@ -275,7 +293,8 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if backup.Status.Phase != velerov1api.BackupPhaseNew && backup.Status.Phase != velerov1api.BackupPhaseInProgress {
|
||||
if backup.Status.Phase == velerov1api.BackupPhaseFailedValidation || backup.Status.Phase == velerov1api.BackupPhaseCompleted ||
|
||||
backup.Status.Phase == velerov1api.BackupPhasePartiallyFailed || backup.Status.Phase == velerov1api.BackupPhaseFailed {
|
||||
fmt.Printf("\nBackup completed with status: %s. You may check for more information using the commands `velero backup describe %s` and `velero backup logs %s`.\n", backup.Status.Phase, backup.Name, backup.Name)
|
||||
return nil
|
||||
}
|
||||
@@ -297,13 +316,13 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
func ParseOrderedResources(orderMapStr string) (map[string]string, error) {
|
||||
entries := strings.Split(orderMapStr, ";")
|
||||
if len(entries) == 0 {
|
||||
return nil, fmt.Errorf("Invalid OrderedResources '%s'.", orderMapStr)
|
||||
return nil, fmt.Errorf("invalid OrderedResources '%s'", orderMapStr)
|
||||
}
|
||||
orderedResources := make(map[string]string)
|
||||
for _, entry := range entries {
|
||||
kv := strings.Split(entry, "=")
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("Invalid OrderedResources '%s'.", entry)
|
||||
return nil, fmt.Errorf("invalid OrderedResources '%s'", entry)
|
||||
}
|
||||
kind := strings.TrimSpace(kv[0])
|
||||
order := strings.TrimSpace(kv[1])
|
||||
@@ -331,11 +350,16 @@ func (o *CreateOptions) BuildBackup(namespace string) (*velerov1api.Backup, erro
|
||||
ExcludedNamespaces(o.ExcludeNamespaces...).
|
||||
IncludedResources(o.IncludeResources...).
|
||||
ExcludedResources(o.ExcludeResources...).
|
||||
IncludedClusterScopedResources(o.IncludeClusterScopedResources...).
|
||||
ExcludedClusterScopedResources(o.ExcludeClusterScopedResources...).
|
||||
IncludedNamespaceScopedResources(o.IncludeNamespaceScopedResources...).
|
||||
ExcludedNamespaceScopedResources(o.ExcludeNamespaceScopedResources...).
|
||||
LabelSelector(o.Selector.LabelSelector).
|
||||
TTL(o.TTL).
|
||||
StorageLocation(o.StorageLocation).
|
||||
VolumeSnapshotLocations(o.SnapshotLocations...).
|
||||
CSISnapshotTimeout(o.CSISnapshotTimeout)
|
||||
CSISnapshotTimeout(o.CSISnapshotTimeout).
|
||||
ItemOperationTimeout(o.ItemOperationTimeout)
|
||||
if len(o.OrderedResources) > 0 {
|
||||
orders, err := ParseOrderedResources(o.OrderedResources)
|
||||
if err != nil {
|
||||
@@ -353,8 +377,23 @@ func (o *CreateOptions) BuildBackup(namespace string) (*velerov1api.Backup, erro
|
||||
if o.DefaultVolumesToFsBackup.Value != nil {
|
||||
backupBuilder.DefaultVolumesToFsBackup(*o.DefaultVolumesToFsBackup.Value)
|
||||
}
|
||||
if o.ResPoliciesConfigmap != "" {
|
||||
backupBuilder.ResourcePolicies(o.ResPoliciesConfigmap)
|
||||
}
|
||||
}
|
||||
|
||||
backup := backupBuilder.ObjectMeta(builder.WithLabelsMap(o.Labels.Data())).Result()
|
||||
return backup, nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) oldAndNewFilterParametersUsedTogether() bool {
|
||||
haveOldResourceFilterParameters := len(o.IncludeResources) > 0 ||
|
||||
len(o.ExcludeResources) > 0 ||
|
||||
o.IncludeClusterResources.Value != nil
|
||||
haveNewResourceFilterParameters := len(o.IncludeClusterScopedResources) > 0 ||
|
||||
(len(o.ExcludeClusterScopedResources) > 0) ||
|
||||
(len(o.IncludeNamespaceScopedResources) > 0) ||
|
||||
(len(o.ExcludeNamespaceScopedResources) > 0)
|
||||
|
||||
return haveOldResourceFilterParameters && haveNewResourceFilterParameters
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ func TestCreateOptions_BuildBackup(t *testing.T) {
|
||||
o.OrderedResources = "pods=p1,p2;persistentvolumeclaims=pvc1,pvc2"
|
||||
orders, err := ParseOrderedResources(o.OrderedResources)
|
||||
o.CSISnapshotTimeout = 20 * time.Minute
|
||||
o.ItemOperationTimeout = 20 * time.Minute
|
||||
assert.NoError(t, err)
|
||||
|
||||
backup, err := o.BuildBackup(testNamespace)
|
||||
@@ -49,6 +50,7 @@ func TestCreateOptions_BuildBackup(t *testing.T) {
|
||||
IncludeClusterResources: o.IncludeClusterResources.Value,
|
||||
OrderedResources: orders,
|
||||
CSISnapshotTimeout: metav1.Duration{Duration: o.CSISnapshotTimeout},
|
||||
ItemOperationTimeout: metav1.Duration{Duration: o.ItemOperationTimeout},
|
||||
}, backup.Spec)
|
||||
|
||||
assert.Equal(t, map[string]string{
|
||||
|
||||
@@ -41,6 +41,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||
listOptions metav1.ListOptions
|
||||
details bool
|
||||
insecureSkipTLSVerify bool
|
||||
outputFormat = "plaintext"
|
||||
)
|
||||
|
||||
config, err := client.LoadConfig()
|
||||
@@ -59,6 +60,10 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||
kbClient, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
if outputFormat != "plaintext" && outputFormat != "json" {
|
||||
cmd.CheckError(fmt.Errorf("Invalid output format '%s'. Valid value are 'plaintext, json'", outputFormat))
|
||||
}
|
||||
|
||||
var backups *velerov1api.BackupList
|
||||
if len(args) > 0 {
|
||||
backups = new(velerov1api.BackupList)
|
||||
@@ -102,13 +107,21 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
s := output.DescribeBackup(context.Background(), kbClient, &backups.Items[i], deleteRequestList.Items, podVolumeBackupList.Items, vscList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile)
|
||||
if first {
|
||||
first = false
|
||||
// structured output only applies to a single backup in case of OOM
|
||||
// To describe the list of backups in structured format, users could iterate over the list and describe backup one after another.
|
||||
if len(backups.Items) == 1 && outputFormat != "plaintext" {
|
||||
s := output.DescribeBackupInSF(context.Background(), kbClient, &backups.Items[i], deleteRequestList.Items, podVolumeBackupList.Items, vscList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile, outputFormat)
|
||||
fmt.Print(s)
|
||||
} else {
|
||||
fmt.Printf("\n\n%s", s)
|
||||
s := output.DescribeBackup(context.Background(), kbClient, &backups.Items[i], deleteRequestList.Items, podVolumeBackupList.Items, vscList.Items, details, veleroClient, insecureSkipTLSVerify, caCertFile)
|
||||
if first {
|
||||
first = false
|
||||
fmt.Print(s)
|
||||
} else {
|
||||
fmt.Printf("\n\n%s", s)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
cmd.CheckError(err)
|
||||
},
|
||||
@@ -118,5 +131,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||
c.Flags().BoolVar(&details, "details", details, "Display additional detail in the command output.")
|
||||
c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.")
|
||||
c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "Path to a certificate bundle to use when verifying TLS connections.")
|
||||
c.Flags().StringVarP(&outputFormat, "output", "o", outputFormat, "Output display format. Valid formats are 'plaintext, json'. 'json' only applies to a single backup")
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
|
||||
}
|
||||
|
||||
switch backup.Status.Phase {
|
||||
case velerov1api.BackupPhaseCompleted, velerov1api.BackupPhasePartiallyFailed, velerov1api.BackupPhaseFailed:
|
||||
// terminal phases, do nothing.
|
||||
case velerov1api.BackupPhaseCompleted, velerov1api.BackupPhasePartiallyFailed, velerov1api.BackupPhaseFailed, velerov1api.BackupPhaseWaitingForPluginOperations, velerov1api.BackupPhaseWaitingForPluginOperationsPartiallyFailed:
|
||||
// terminal and waiting for plugin operations phases, do nothing.
|
||||
default:
|
||||
cmd.Exit("Logs for backup %q are not available until it's finished processing. Please wait "+
|
||||
"until the backup has a phase of Completed or Failed and try again.", backupName)
|
||||
|
||||
@@ -19,7 +19,7 @@ package backuplocation
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -141,7 +141,7 @@ func (o *CreateOptions) BuildBackupStorageLocation(namespace string, setBackupSy
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertData, err = ioutil.ReadFile(realPath)
|
||||
caCertData, err = os.ReadFile(realPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func NewDeleteCommand(f client.Factory, use string) *cobra.Command {
|
||||
# Delete backup storage locations named "backup-location-1" and "backup-location-2".
|
||||
velero backup-location delete backup-location-1 backup-location-2
|
||||
|
||||
# Delete all backup storage locations labelled with "foo=bar".
|
||||
# Delete all backup storage locations labeled with "foo=bar".
|
||||
velero backup-location delete --selector foo=bar
|
||||
|
||||
# Delete all backup storage locations.
|
||||
|
||||
@@ -19,7 +19,7 @@ package backuplocation
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -98,7 +98,7 @@ func (o *SetOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
caCertData, err = ioutil.ReadFile(realPath)
|
||||
caCertData, err = os.ReadFile(realPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ about: Tell us about a problem you are experiencing
|
||||
---
|
||||
|
||||
**What steps did you take and what happened:**
|
||||
[A clear and concise description of what the bug is, and what commands you ran.)
|
||||
<!--A clear and concise description of what the bug is, and what commands you ran.-->
|
||||
|
||||
|
||||
**What did you expect to happen:**
|
||||
@@ -72,7 +72,7 @@ Please provide the output of the following commands (Pasting long output into a
|
||||
|
||||
|
||||
**Anything else you would like to add:**
|
||||
[Miscellaneous information that will assist in solving the issue.]
|
||||
<!--Miscellaneous information that will assist in solving the issue.-->
|
||||
|
||||
|
||||
**Environment:**
|
||||
@@ -171,7 +171,7 @@ func getKubectlVersion() (string, error) {
|
||||
}
|
||||
}
|
||||
versionOut := outbuf.String()
|
||||
kubectlVersion := strings.TrimSpace(string(versionOut))
|
||||
kubectlVersion := strings.TrimSpace(versionOut)
|
||||
return kubectlVersion, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -69,11 +68,6 @@ func (o *option) bindFlags(flags *pflag.FlagSet) {
|
||||
flags.BoolVar(&o.verbose, "verbose", false, "When it's set to true the debug messages by crashd will be printed during execution. Default value is false.")
|
||||
}
|
||||
|
||||
func (o *option) asCrashdArgs() string {
|
||||
return fmt.Sprintf("output=%s,namespace=%s,basedir=%s,backup=%s,restore=%s,kubeconfig=%s,kubecontext=%s",
|
||||
o.outputPath, o.namespace, o.baseDir, o.backup, o.restore, o.kubeconfigPath, o.kubeContext)
|
||||
}
|
||||
|
||||
func (o *option) asCrashdArgMap() exec.ArgMap {
|
||||
return exec.ArgMap{
|
||||
"cmd": o.currCmd,
|
||||
@@ -96,7 +90,7 @@ func (o *option) complete(f client.Factory, fs *pflag.FlagSet) error {
|
||||
return fmt.Errorf("invalid output path: %v", err)
|
||||
}
|
||||
o.outputPath = absOutputPath
|
||||
tmpDir, err := ioutil.TempDir("", "crashd")
|
||||
tmpDir, err := os.MkdirTemp("", "crashd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user