Merge branch 'main' into issue-fix-6663

This commit is contained in:
Lyndon-Li
2023-10-27 17:44:17 +08:00
89 changed files with 2316 additions and 574 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
go-version: '1.21'
id: go
# Look for a CLI that's made for this PR
- name: Fetch built CLI
+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
go-version: '1.21'
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@v4
with:
go-version: '1.20'
go-version: '1.21'
id: go
- name: Check out the code
uses: actions/checkout@v2
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
go-version: '1.21'
id: go
- name: Check out the code
uses: actions/checkout@v2
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
go-version: '1.21'
id: go
- uses: actions/checkout@v3
+2 -2
View File
@@ -13,7 +13,7 @@
# limitations under the License.
# Velero binary build section
FROM --platform=$BUILDPLATFORM golang:1.20-bullseye as velero-builder
FROM --platform=$BUILDPLATFORM golang:1.21-bookworm as velero-builder
ARG GOPROXY
ARG BIN
@@ -47,7 +47,7 @@ RUN mkdir -p /output/usr/bin && \
go clean -modcache -cache
# Restic binary build section
FROM --platform=$BUILDPLATFORM golang:1.20-bullseye as restic-builder
FROM --platform=$BUILDPLATFORM golang:1.21-bookworm as restic-builder
ARG BIN
ARG TARGETOS
+1 -1
View File
@@ -52,7 +52,7 @@ git_sha = str(local("git rev-parse HEAD", quiet = True, echo_off = True)).strip(
tilt_helper_dockerfile_header = """
# Tilt image
FROM golang:1.20 as tilt-helper
FROM golang:1.21 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 && \
+1
View File
@@ -0,0 +1 @@
Perf improvements for existing resource restore
+1
View File
@@ -0,0 +1 @@
Fix the node-agent missing metrics-address defines.
+1
View File
@@ -0,0 +1 @@
Add volume types filter in resource policies
@@ -0,0 +1 @@
Fix issue 6913: Velero Built-in Datamover: Backup stucks in phase WaitingForPluginOperations when Node Agent pod gets restarted
+1
View File
@@ -0,0 +1 @@
Optional check if targeted container is ready before executing a hook
+1
View File
@@ -0,0 +1 @@
Bump up aws sdk to aws-sdk-go-v2
+1
View File
@@ -0,0 +1 @@
Add MSI Support for Azure plugin.
+1
View File
@@ -0,0 +1 @@
Fix #6668, add a limitation for file system restore parallelism with other types of restores (CSI snapshot restore, CSI snapshot movement restore)
+1
View File
@@ -0,0 +1 @@
Import auth provider plugins
+1
View File
@@ -0,0 +1 @@
Check whether the action is a CSI action and whether CSI feature is enabled, before executing the action.
+1
View File
@@ -0,0 +1 @@
It is a valid case that the Status.RestoreSize field in VolumeSnapshot is not set, if so, get the volume size from the source PVC to create the backup PVC
+1
View File
@@ -0,0 +1 @@
Limit PVC block mode logic to non-Windows platform.
+1
View File
@@ -0,0 +1 @@
Fix #6988, always get region from BSL if it is not empty
+1
View File
@@ -0,0 +1 @@
Bump kopia to 0.15.0
+1
View File
@@ -0,0 +1 @@
Fix #6964. Don't use csiSnapshotTimeout (10 min) for waiting snapshot to readyToUse for data mover, so as to make the behavior complied with CSI snapshot backup
+126
View File
@@ -0,0 +1,126 @@
# Velero Uploader Configuration Integration and Extensibility
## Abstract
This design proposal aims to make Velero Uploader configurable by introducing a structured approach for managing Uploader settings. we will define and standardize a data structure to facilitate future additions to Uploader configurations. This enhancement provides a template for extending Uploader-related options. And also includes examples of adding sub-options to the Uploader Configuration.
## Background
Velero is widely used for backing up and restoring Kubernetes clusters. In various scenarios, optimizing the backup process is essential, future needs may arise for adding more configuration options related to the Uploader component especially when dealing with large datasets. Therefore, a standardized configuration template is required.
## Goals
1. **Extensible Uploader Configuration**: Provide an extensible approach to manage Uploader configurations, making it easy to add and modify configuration options related to the Velero uploader.
2. **User-friendliness**: Ensure that the new Uploader configuration options are easy to understand and use for Velero users without introducing excessive complexity.
## Non Goals
1. Expanding to other Velero components: The primary focus of this design is Uploader configuration and does not include extending to other components or modules within Velero. Configuration changes for other components may require separate design and implementation.
## High-Level Design
To achieve extensibility in Velero Uploader configurations, the following key components and changes are proposed:
### UploaderConfig Structure
A new data structure, `UploaderConfig`, will be defined to store Uploader configurations. This structure will include the configuration options related to backup and restore for Uploader:
```go
type UploaderConfig struct {
// sub-options
}
```
### Integration with Backup CRD
The Velero CLI will support an uploader configuration-related flag, allowing users to set the value when creating backups or restores. This value will be stored in the `UploaderConfig` field within the `Backup` CRD and `Restore` CRD:
```go
type BackupSpec struct {
UploaderConfig shared.UploaderConfig `json:"uploaderConfig,omitempty"`
}
type RestoreSpec struct {
UploaderConfig shared.UploaderConfig `json:"uploaderConfig,omitempty"`
}
```
### Configuration Propagated to Different CRDs
The configuration specified in `UploaderConfig` needs to be effective for backup and restore both by file system way and data-mover way.
Therefore, the `UploaderConfig` field value from the `Backup` CRD should be propagated to `PodVolumeBackup` and `DataUpload` CRDs:
```go
type PodVolumeBackupSpec struct {
...
UploaderConfig shared.UploaderConfig `json:"uploaderConfig,omitempty"`
}
type DataUploadSpec struct {
...
UploaderConfig shared.UploaderConfig `json:"uploaderConfig,omitempty"`
}
```
Also the `UploaderConfig` field value from the `Restore` CRD should be propagated to `PodVolumeRestore` and `DataDownload` CRDs:
```go
type PodVolumeRestoreSpec struct {
...
UploaderConfig shared.UploaderConfig `json:"uploaderConfig,omitempty"`
}
type DataDownloadSpec struct {
...
UploaderConfig shared.UploaderConfig `json:"uploaderConfig,omitempty"`
}
```
## Sub-options in UploaderConfig
Adding fields above in CRDs can accommodate any future additions to Uploader configurations by adding new fields to the `UploaderConfig` structure.
### Parallel Files Upload
This section focuses on enabling the configuration for the number of parallel file uploads during backups.
below are the key steps that should be added to support this new feature.
#### Velero CLI
The Velero CLI will support a `--parallel-files-upload` flag, allowing users to set the `ParallelFilesUpload` value when creating backups.
#### UploaderConfig
below the sub-option `ParallelFilesUpload` is added into UploaderConfig:
```go
type UploaderConfig struct {
ParallelFilesUpload int `json:"parallelFilesUpload,omitempty"`
}
```
#### Kopia Parallel Upload Policy
Velero Uploader can set upload policies when calling Kopia APIs. In the Kopia codebase, the structure for upload policies is defined as follows:
```go
// UploadPolicy describes the policy to apply when uploading snapshots.
type UploadPolicy struct {
...
MaxParallelFileReads *OptionalInt `json:"maxParallelFileReads,omitempty"`
}
```
Velero can set the `MaxParallelFileReads` parameter for Kopia's upload policy as follows:
```go
curPolicy := getDefaultPolicy()
if uploaderCfg.ParallelFilesUpload > 0 {
curPolicy.UploadPolicy.MaxParallelFileReads = newOptionalInt(uploaderCfg.ParallelFilesUpload)
}
```
#### Restic Parallel Upload Policy
As Restic does not support parallel file upload, the configuration would not take effect, so we should output a warning when the user sets the `ParallelFilesUpload` value by using Restic to do a backup.
```go
if uploaderCfg.ParallelFilesUpload > 0 {
log.Warnf("ParallelFilesUpload is set to %d, but Restic does not support parallel file uploads. Ignoring", uploaderCfg.ParallelFilesUpload)
}
```
Roughly, the process is as follows:
1. Users pass the ParallelFilesUpload parameter and its value through the Velero CLI. This parameter and its value are stored as a sub-option within UploaderConfig and then placed into the Backup CR.
2. When users perform file system backups, UploaderConfig is passed to the PodVolumeBackup CR. When users use the Data-mover for backups, it is passed to the DataUpload CR.
3. Each respective controller within the CRs calls the uploader, and the ParallelFilesUpload from UploaderConfig in CRs is passed to the uploader.
4. When the uploader subsequently calls the Kopia API, it can use the ParallelFilesUpload to set the MaxParallelFileReads parameter, and if the uploader calls the Restic command it would output one warning log for Restic does not support this feature.
## Alternatives Considered
To enhance extensibility further, the option of storing `UploaderConfig` in a Kubernetes ConfigMap can be explored, this approach would allow the addition and modification of configuration options without the need to modify the CRD.
@@ -666,6 +666,9 @@ At present, VBDM doesn't support recovery, so it will follow the second rule.
## Kopia For Block Device
To work with block devices, VGDP will be updated. Today, when Kopia attempts to create a snapshot of the block device, it will error because kopia does not support this file type. Kopia does have a nice set of interfaces that are able to be extended though.
**Notice**
The Kopia block mode uploader only supports non-Windows platforms, because the block mode code invokes some system calls that are not present in the Windows platform.
To achieve the necessary information to determine the type of volume that is being used, we will need to pass in the volume mode in provider interface.
```go
+46 -27
View File
@@ -5,10 +5,10 @@ go 1.21
toolchain go1.21.3
require (
cloud.google.com/go/storage v1.32.0
cloud.google.com/go/storage v1.33.0
github.com/Azure/azure-pipeline-go v0.2.3
github.com/Azure/azure-sdk-for-go v67.2.0+incompatible
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
@@ -16,7 +16,11 @@ require (
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.44.256
github.com/aws/aws-sdk-go-v2 v1.21.0
github.com/aws/aws-sdk-go-v2/config v1.18.42
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.87
github.com/aws/aws-sdk-go-v2/service/ec2 v1.123.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0
github.com/bombsimon/logrusr/v3 v3.0.0
github.com/evanphx/json-patch v5.6.0+incompatible
github.com/fatih/color v1.15.0
@@ -32,7 +36,7 @@ require (
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.20.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.16.0
github.com/prometheus/client_golang v1.17.0
github.com/robfig/cron v1.1.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/afero v1.6.0
@@ -40,14 +44,14 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
github.com/vmware-tanzu/crash-diagnostics v0.3.7
go.uber.org/zap v1.25.0
go.uber.org/zap v1.26.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/mod v0.12.0
golang.org/x/net v0.14.0
golang.org/x/oauth2 v0.11.0
golang.org/x/mod v0.13.0
golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.13.0
golang.org/x/text v0.13.0
google.golang.org/api v0.138.0
google.golang.org/grpc v1.57.0
google.golang.org/api v0.146.0
google.golang.org/grpc v1.58.3
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.25.6
@@ -64,7 +68,7 @@ require (
)
require (
cloud.google.com/go v0.110.6 // indirect
cloud.google.com/go v0.110.7 // indirect
cloud.google.com/go/compute v1.23.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.1 // indirect
@@ -77,6 +81,21 @@ require (
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect
github.com/aws/smithy-go v1.14.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chmduquesne/rollinghash v4.0.0+incompatible // indirect
@@ -99,8 +118,8 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/s2a-go v0.1.5 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/hashicorp/cronexpr v1.1.2 // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
@@ -109,7 +128,7 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/klauspost/reedsolomon v1.11.8 // indirect
@@ -135,30 +154,30 @@ require (
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/vladimirvivien/gexe v0.1.1 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.17.0 // indirect
go.opentelemetry.io/otel/metric v1.17.0 // indirect
go.opentelemetry.io/otel/trace v1.17.0 // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.starlark.net v0.0.0-20201006213952-227f4aabceb5 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/term v0.12.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
@@ -169,4 +188,4 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
)
replace github.com/kopia/kopia => github.com/project-velero/kopia v0.0.0-20230918080509-48b07dfffc74
replace github.com/kopia/kopia => github.com/project-velero/kopia v0.0.0-20231023031817-cf7bbc7f8519
+95 -77
View File
@@ -19,8 +19,8 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.110.6 h1:8uYAkj3YHTP/1iwReuHPxLSbdcyc+dSBbzFMrVwDR6Q=
cloud.google.com/go v0.110.6/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o=
cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -45,15 +45,15 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.32.0 h1:5w6DxEGOnktmJHarxAOUywxVW9lbNWIzlzzUltG/3+o=
cloud.google.com/go/storage v1.32.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8=
cloud.google.com/go/storage v1.33.0 h1:PVrDOkIC8qQVa1P3SXGpQvfuJhN2LHOoyZvWs8D2X5M=
cloud.google.com/go/storage v1.33.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8=
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 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 v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 h1:9kDVnTz3vbfweTqAUmk/a/pH5pWFCHtvRpHYC0G/dcA=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0/go.mod h1:3Ug6Qzto9anB6mGlEdgYMDF5zHQ+wwhEaYR4s17PHMw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 h1:LNHhpdK7hzUcx/k1LIcuh5k7k1LGIWLQfCjaneSj7Fc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
@@ -140,12 +140,48 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go v1.44.256 h1:O8VH+bJqgLDguqkH/xQBFz5o/YheeZqgcOYIgsTVWY4=
github.com/aws/aws-sdk-go v1.44.256/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 h1:OPLEkmhXf6xFPiz0bLeDArZIDx1NNS4oJyG4nv3Gct0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13/go.mod h1:gpAbvyDGQFozTEmlTFO8XcQKHzubdq0LzRyJpG6MiXM=
github.com/aws/aws-sdk-go-v2/config v1.18.42 h1:28jHROB27xZwU0CB88giDSjz7M1Sba3olb5JBGwina8=
github.com/aws/aws-sdk-go-v2/config v1.18.42/go.mod h1:4AZM3nMMxwlG+eZlxvBKqwVbkDLlnN2a4UGTL6HjaZI=
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 h1:s8yOkDh+5b1jUDhMBtngF6zKWLDs84chUk2Vk0c38Og=
github.com/aws/aws-sdk-go-v2/credentials v1.13.40/go.mod h1:VtEHVAAqDWASwdOqj/1huyT6uHbs5s8FUHfDQdky/Rs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.87 h1:e20ZrsgDPUXqg8+rZVuPwNSp6yniUN2Yr2tzFZ+Yvl0=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.87/go.mod h1:0i0TAT6W+5i48QTlDU2KmY6U2hBZeY/LCP0wktya2oc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 h1:g+qlObJH4Kn4n21g69DjspU0hKTjWtq7naZ9OLCv0ew=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 h1:6lJvvkQ9HmbHZ4h/IEwclwv2mrTW8Uq1SOB/kXy0mfw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4/go.mod h1:1PrKYwxTM+zjpw9Y41KFtoJCQrJ34Z47Y4VgVbfndjo=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.123.0 h1:AkjjaINgZQlz3valIVmlrs18jsl+fzYuoxED8oOVrYo=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.123.0/go.mod h1:0FhI2Rzcv5BNM3dNnbcCx2qa2naFZoAidJi11cQgzL0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 h1:m0QTSI6pZYJTk5WSKx3fm5cNW/DCicVzULBgU/6IyD0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14/go.mod h1:dDilntgHy9WnHXsh7dDtUPgHKEfTJIBUTHM8OWm0f/0=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 h1:eev2yZX7esGRjqRbnVk1UxMLw4CyVZDpZXRCcy75oQk=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36/go.mod h1:lGnOkH9NJATw0XEPcAknFBj3zzNTEGRHtSw+CwC1YTg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 h1:v0jkRigbSD6uOdwcaUQmgEwG1BkPfAPDqaeNt/29ghg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4/go.mod h1:LhTyt8J04LL+9cIt7pYJ5lbS/U98ZmXovLOR/4LUsk8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0 h1:wl5dxN1NONhTDQD9uaEvNsDRX29cBmGED/nl0jkWlt4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0/go.mod h1:rDGMZA7f4pbmTtPOk5v5UM2lmX6UAbRnMDJeDvnH7AM=
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 h1:YkNzx1RLS0F5qdf9v1Q8Cuv9NXCL2TkosOxhzlUPV64=
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 h1:8lKOidPkmSmfUtiTgtdXWgaKItCZ/g75/jEk6Ql6GsA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4=
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 h1:s4bioTgjSFRwOoyEFzAVCmFmoowBgjTR8gkrF/sQ4wk=
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU=
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -175,11 +211,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
@@ -233,7 +265,6 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -375,6 +406,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -399,8 +431,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.5 h1:8IYp3w9nysqv3JH+NJgXJzGbDHzLOTj43BmSkp+O7qg=
github.com/google/s2a-go v0.1.5/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -408,8 +440,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
@@ -499,8 +531,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
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.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
@@ -511,8 +543,8 @@ github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7v
github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kopia/htmluibuild v0.0.1-0.20230917154246-98806054261e h1:XogFUFI4mcT5qyywKiGY5WqLi7l4b/eMi7BlEzgLTd0=
github.com/kopia/htmluibuild v0.0.1-0.20230917154246-98806054261e/go.mod h1:cSImbrlwvv2phvj5RfScL2v08ghX6xli0PcK6f+t8S0=
github.com/kopia/htmluibuild v0.0.1-0.20231019063300-75c2a788c7d0 h1:TvupyyfbUZzsO4DQJpQhKZnUa61xERcJ+ejCbHWG2NY=
github.com/kopia/htmluibuild v0.0.1-0.20231019063300-75c2a788c7d0/go.mod h1:cSImbrlwvv2phvj5RfScL2v08ghX6xli0PcK6f+t8S0=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -639,22 +671,22 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/project-velero/kopia v0.0.0-20230918080509-48b07dfffc74 h1:dLMdxdRegzp3DhTa7nc9c37n8cukHLIK8SXRTOh/3pg=
github.com/project-velero/kopia v0.0.0-20230918080509-48b07dfffc74/go.mod h1:eFPSn8Dy0XlRUbIDZjEaDKmFdJMh5lySvtF6eyl5j/s=
github.com/project-velero/kopia v0.0.0-20231023031817-cf7bbc7f8519 h1:DiikAMR1wBIY6oFoN76WEJz4f+6OM99ZiGzZ9m9v32I=
github.com/project-velero/kopia v0.0.0-20231023031817-cf7bbc7f8519/go.mod h1:V/zpEMjxzqEf3lF52m0b0nAIVQGolPYIOXRjVxeK1j0=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
@@ -669,8 +701,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/robfig/cron v1.1.0 h1:jk4/Hud3TTdcrJgUOBgsqrZBarcxl6ADIjSC2iniwLY=
github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
@@ -763,7 +795,6 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
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.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
@@ -800,19 +831,19 @@ go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUz
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM=
go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0=
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc=
go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o=
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ=
go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY=
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
go.starlark.net v0.0.0-20201006213952-227f4aabceb5 h1:ApvY/1gw+Yiqb/FKeks3KnVPWpkR3xzij82XPKLjJVw=
@@ -830,8 +861,8 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -849,9 +880,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5
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-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -890,9 +920,8 @@ 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.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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=
@@ -948,10 +977,8 @@ golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
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=
@@ -967,8 +994,8 @@ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -980,9 +1007,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1059,22 +1085,18 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.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/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
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=
@@ -1084,8 +1106,6 @@ 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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1160,7 +1180,6 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
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=
@@ -1191,8 +1210,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/api v0.138.0 h1:K/tVp05MxNVbHShRw9m7e9VJGdagNeTdMzqPH7AUqr0=
google.golang.org/api v0.138.0/go.mod h1:4xyob8CxC+0GChNBvEUAk8VBKNvYOTWM9T3v3UfRxuY=
google.golang.org/api v0.146.0 h1:9aBYT4vQXt9dhCuLNfwfd3zpwu8atg0yPkjBymwSrOM=
google.golang.org/api v0.146.0/go.mod h1:OARJqIfoYjXJj4C1AiBSXYZt03qsoz8FQYU6fBEfrHM=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1248,12 +1267,12 @@ google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaE
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g=
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8=
google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44=
google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 h1:wukfNtZmZUurLN/atp2hiIeTKn7QJWIQdHzqmsOnAOk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA=
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI=
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@@ -1278,9 +1297,8 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+1 -1
View File
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
FROM --platform=linux/amd64 golang:1.20-bullseye
FROM --platform=linux/amd64 golang:1.21-bookworm
ARG GOPROXY
+29 -16
View File
@@ -1,36 +1,49 @@
diff --git a/go.mod b/go.mod
index 5f939c481..6f281b45d 100644
index 5f939c481..3f5ea4096 100644
--- a/go.mod
+++ b/go.mod
@@ -25,12 +25,12 @@ require (
@@ -24,13 +24,13 @@ require (
github.com/restic/chunker v0.4.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.5.0
- golang.org/x/crypto v0.5.0
- golang.org/x/net v0.5.0
+ golang.org/x/net v0.7.0
+ golang.org/x/crypto v0.14.0
+ golang.org/x/net v0.17.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
+ golang.org/x/sys v0.13.0
+ golang.org/x/term v0.13.0
+ golang.org/x/text v0.13.0
google.golang.org/api v0.106.0
)
diff --git a/go.sum b/go.sum
index 026e1d2fa..da35b7a6c 100644
index 026e1d2fa..f24fef5b8 100644
--- a/go.sum
+++ b/go.sum
@@ -172,8 +172,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
-golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -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/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
@@ -40,21 +53,21 @@ index 026e1d2fa..da35b7a6c 100644
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-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/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-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/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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=
@@ -119,6 +119,7 @@ func InvokeDeleteActions(ctx *Context) error {
if !action.Selector.Matches(labels.Set(obj.GetLabels())) {
continue
}
err = action.DeleteItemAction.Execute(&velero.DeleteItemActionExecuteInput{
Item: obj,
Backup: ctx.Backup,
+23 -5
View File
@@ -19,6 +19,7 @@ package hook
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
@@ -37,6 +38,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/podexec"
"github.com/vmware-tanzu/velero/pkg/restorehelper"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/collections"
"github.com/vmware-tanzu/velero/pkg/util/kube"
)
@@ -61,6 +63,7 @@ const (
podRestoreHookOnErrorAnnotationKey = "post.hook.restore.velero.io/on-error"
podRestoreHookTimeoutAnnotationKey = "post.hook.restore.velero.io/exec-timeout"
podRestoreHookWaitTimeoutAnnotationKey = "post.hook.restore.velero.io/wait-timeout"
podRestoreHookWaitForReadyAnnotationKey = "post.hook.restore.velero.io/wait-for-ready"
podRestoreHookInitContainerImageAnnotationKey = "init.hook.restore.velero.io/container-image"
podRestoreHookInitContainerNameAnnotationKey = "init.hook.restore.velero.io/container-name"
podRestoreHookInitContainerCommandAnnotationKey = "init.hook.restore.velero.io/command"
@@ -477,12 +480,23 @@ func getPodExecRestoreHookFromAnnotations(annotations map[string]string, log log
}
}
waitForReadyString := annotations[podRestoreHookWaitForReadyAnnotationKey]
waitForReady := boolptr.False()
if waitForReadyString != "" {
var err error
*waitForReady, err = strconv.ParseBool(waitForReadyString)
if err != nil {
log.Warn(errors.Wrapf(err, "Unable to parse wait for ready %s, ignoring", waitForReadyString))
}
}
return &velerov1api.ExecRestoreHook{
Container: container,
Command: parseStringToCommand(commandValue),
OnError: onError,
ExecTimeout: metav1.Duration{Duration: execTimeout},
WaitTimeout: metav1.Duration{Duration: waitTimeout},
Container: container,
Command: parseStringToCommand(commandValue),
OnError: onError,
ExecTimeout: metav1.Duration{Duration: execTimeout},
WaitTimeout: metav1.Duration{Duration: waitTimeout},
WaitForReady: waitForReady,
}
}
@@ -542,6 +556,10 @@ func GroupRestoreExecHooks(
Hook: *rh.Exec,
HookSource: "backupSpec",
}
// default to false if attr WaitForReady not set
if named.Hook.WaitForReady == nil {
named.Hook.WaitForReady = boolptr.False()
}
// default to first container in pod if unset, without mutating resource restore hook
if named.Hook.Container == "" {
named.Hook.Container = pod.Spec.Containers[0].Name
+87 -67
View File
@@ -36,6 +36,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/collections"
)
@@ -724,7 +725,8 @@ func TestGetPodExecRestoreHookFromAnnotations(t *testing.T) {
podRestoreHookCommandAnnotationKey: "/usr/bin/foo",
},
expected: &velerov1api.ExecRestoreHook{
Command: []string{"/usr/bin/foo"},
Command: []string{"/usr/bin/foo"},
WaitForReady: boolptr.False(),
},
},
{
@@ -733,7 +735,8 @@ func TestGetPodExecRestoreHookFromAnnotations(t *testing.T) {
podRestoreHookCommandAnnotationKey: `["a","b","c"]`,
},
expected: &velerov1api.ExecRestoreHook{
Command: []string{"a", "b", "c"},
Command: []string{"a", "b", "c"},
WaitForReady: boolptr.False(),
},
},
{
@@ -743,8 +746,9 @@ func TestGetPodExecRestoreHookFromAnnotations(t *testing.T) {
podRestoreHookOnErrorAnnotationKey: string(velerov1api.HookErrorModeContinue),
},
expected: &velerov1api.ExecRestoreHook{
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
WaitForReady: boolptr.False(),
},
},
{
@@ -754,8 +758,9 @@ func TestGetPodExecRestoreHookFromAnnotations(t *testing.T) {
podRestoreHookOnErrorAnnotationKey: string(velerov1api.HookErrorModeFail),
},
expected: &velerov1api.ExecRestoreHook{
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeFail,
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeFail,
WaitForReady: boolptr.False(),
},
},
{
@@ -766,9 +771,10 @@ func TestGetPodExecRestoreHookFromAnnotations(t *testing.T) {
podRestoreHookWaitTimeoutAnnotationKey: "1h",
},
expected: &velerov1api.ExecRestoreHook{
Command: []string{"/usr/bin/foo"},
ExecTimeout: metav1.Duration{Duration: 45 * time.Second},
WaitTimeout: metav1.Duration{Duration: time.Hour},
Command: []string{"/usr/bin/foo"},
ExecTimeout: metav1.Duration{Duration: 45 * time.Second},
WaitTimeout: metav1.Duration{Duration: time.Hour},
WaitForReady: boolptr.False(),
},
},
{
@@ -778,8 +784,9 @@ func TestGetPodExecRestoreHookFromAnnotations(t *testing.T) {
podRestoreHookContainerAnnotationKey: "my-app",
},
expected: &velerov1api.ExecRestoreHook{
Command: []string{"/usr/bin/foo"},
Container: "my-app",
Command: []string{"/usr/bin/foo"},
Container: "my-app",
WaitForReady: boolptr.False(),
},
},
{
@@ -790,9 +797,10 @@ func TestGetPodExecRestoreHookFromAnnotations(t *testing.T) {
podRestoreHookTimeoutAnnotationKey: "none",
},
expected: &velerov1api.ExecRestoreHook{
Command: []string{"/usr/bin/foo"},
Container: "my-app",
ExecTimeout: metav1.Duration{Duration: 0},
Command: []string{"/usr/bin/foo"},
Container: "my-app",
ExecTimeout: metav1.Duration{Duration: 0},
WaitForReady: boolptr.False(),
},
},
{
@@ -803,9 +811,10 @@ func TestGetPodExecRestoreHookFromAnnotations(t *testing.T) {
podRestoreHookWaitTimeoutAnnotationKey: "none",
},
expected: &velerov1api.ExecRestoreHook{
Command: []string{"/usr/bin/foo"},
Container: "my-app",
ExecTimeout: metav1.Duration{Duration: 0},
Command: []string{"/usr/bin/foo"},
Container: "my-app",
ExecTimeout: metav1.Duration{Duration: 0},
WaitForReady: boolptr.False(),
},
},
}
@@ -842,6 +851,7 @@ func TestGroupRestoreExecHooks(t *testing.T) {
podRestoreHookOnErrorAnnotationKey, string(velerov1api.HookErrorModeContinue),
podRestoreHookTimeoutAnnotationKey, "1s",
podRestoreHookWaitTimeoutAnnotationKey, "1m",
podRestoreHookWaitForReadyAnnotationKey, "true",
)).
Containers(&corev1api.Container{
Name: "container1",
@@ -853,11 +863,12 @@ func TestGroupRestoreExecHooks(t *testing.T) {
HookName: "<from-annotation>",
HookSource: "annotation",
Hook: velerov1api.ExecRestoreHook{
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
WaitForReady: boolptr.True(),
},
},
},
@@ -883,11 +894,12 @@ func TestGroupRestoreExecHooks(t *testing.T) {
HookName: "<from-annotation>",
HookSource: "annotation",
Hook: velerov1api.ExecRestoreHook{
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
WaitForReady: boolptr.False(),
},
},
},
@@ -923,11 +935,12 @@ func TestGroupRestoreExecHooks(t *testing.T) {
HookName: "hook1",
HookSource: "backupSpec",
Hook: velerov1api.ExecRestoreHook{
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
WaitForReady: boolptr.False(),
},
},
},
@@ -962,11 +975,12 @@ func TestGroupRestoreExecHooks(t *testing.T) {
HookName: "hook1",
HookSource: "backupSpec",
Hook: velerov1api.ExecRestoreHook{
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
WaitForReady: boolptr.False(),
},
},
},
@@ -1009,11 +1023,12 @@ func TestGroupRestoreExecHooks(t *testing.T) {
HookName: "<from-annotation>",
HookSource: "annotation",
Hook: velerov1api.ExecRestoreHook{
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
WaitForReady: boolptr.False(),
},
},
},
@@ -1105,11 +1120,12 @@ func TestGroupRestoreExecHooks(t *testing.T) {
RestoreHooks: []velerov1api.RestoreResourceHook{
{
Exec: &velerov1api.ExecRestoreHook{
Container: "container1",
Command: []string{"/usr/bin/aaa"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second * 4},
WaitTimeout: metav1.Duration{Duration: time.Minute * 4},
Container: "container1",
Command: []string{"/usr/bin/aaa"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second * 4},
WaitTimeout: metav1.Duration{Duration: time.Minute * 4},
WaitForReady: boolptr.True(),
},
},
},
@@ -1126,33 +1142,36 @@ func TestGroupRestoreExecHooks(t *testing.T) {
HookName: "hook1",
HookSource: "backupSpec",
Hook: velerov1api.ExecRestoreHook{
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeFail,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
Container: "container1",
Command: []string{"/usr/bin/foo"},
OnError: velerov1api.HookErrorModeFail,
ExecTimeout: metav1.Duration{Duration: time.Second},
WaitTimeout: metav1.Duration{Duration: time.Minute},
WaitForReady: boolptr.False(),
},
},
{
HookName: "hook1",
HookSource: "backupSpec",
Hook: velerov1api.ExecRestoreHook{
Container: "container1",
Command: []string{"/usr/bin/bar"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second * 2},
WaitTimeout: metav1.Duration{Duration: time.Minute * 2},
Container: "container1",
Command: []string{"/usr/bin/bar"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second * 2},
WaitTimeout: metav1.Duration{Duration: time.Minute * 2},
WaitForReady: boolptr.False(),
},
},
{
HookName: "hook2",
HookSource: "backupSpec",
Hook: velerov1api.ExecRestoreHook{
Container: "container1",
Command: []string{"/usr/bin/aaa"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second * 4},
WaitTimeout: metav1.Duration{Duration: time.Minute * 4},
Container: "container1",
Command: []string{"/usr/bin/aaa"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second * 4},
WaitTimeout: metav1.Duration{Duration: time.Minute * 4},
WaitForReady: boolptr.True(),
},
},
},
@@ -1161,11 +1180,12 @@ func TestGroupRestoreExecHooks(t *testing.T) {
HookName: "hook1",
HookSource: "backupSpec",
Hook: velerov1api.ExecRestoreHook{
Container: "container2",
Command: []string{"/usr/bin/baz"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second * 3},
WaitTimeout: metav1.Duration{Duration: time.Second * 3},
Container: "container2",
Command: []string{"/usr/bin/baz"},
OnError: velerov1api.HookErrorModeContinue,
ExecTimeout: metav1.Duration{Duration: time.Second * 3},
WaitTimeout: metav1.Duration{Duration: time.Second * 3},
WaitForReady: boolptr.False(),
},
},
},
+14 -3
View File
@@ -29,6 +29,7 @@ import (
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/podexec"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/kube"
)
@@ -126,8 +127,8 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
}
for containerName, hooks := range byContainer {
if !isContainerRunning(newPod, containerName) {
podLog.Infof("Container %s is not running: post-restore hooks will not yet be executed", containerName)
if !isContainerUp(newPod, containerName, hooks) {
podLog.Infof("Container %s is not up: post-restore hooks will not yet be executed", containerName)
continue
}
podMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(newPod)
@@ -243,14 +244,24 @@ func podHasContainer(pod *v1.Pod, containerName string) bool {
return false
}
func isContainerRunning(pod *v1.Pod, containerName string) bool {
func isContainerUp(pod *v1.Pod, containerName string, hooks []PodExecRestoreHook) bool {
if pod == nil {
return false
}
var waitForReady bool
for _, hook := range hooks {
if boolptr.IsSetToTrue(hook.Hook.WaitForReady) {
waitForReady = true
break
}
}
for _, cs := range pod.Status.ContainerStatuses {
if cs.Name != containerName {
continue
}
if waitForReady {
return cs.Ready
}
return cs.State.Running != nil
}
+50 -2
View File
@@ -35,6 +35,7 @@ import (
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
)
type fakeListWatchFactory struct {
@@ -790,12 +791,13 @@ func TestPodHasContainer(t *testing.T) {
}
}
func TestIsContainerRunning(t *testing.T) {
func TestIsContainerUp(t *testing.T) {
tests := []struct {
name string
pod *v1.Pod
container string
expect bool
hooks []PodExecRestoreHook
}{
{
name: "should return true when running",
@@ -809,6 +811,49 @@ func TestIsContainerRunning(t *testing.T) {
},
}).
Result(),
hooks: []PodExecRestoreHook{},
},
{
name: "should return false when running but not ready",
container: "container1",
expect: false,
pod: builder.ForPod("default", "my-pod").
ContainerStatuses(&v1.ContainerStatus{
Name: "container1",
State: v1.ContainerState{
Running: &v1.ContainerStateRunning{},
},
Ready: false,
}).
Result(),
hooks: []PodExecRestoreHook{
{
Hook: velerov1api.ExecRestoreHook{
WaitForReady: boolptr.True(),
},
},
},
},
{
name: "should return true when running and ready",
container: "container1",
expect: true,
pod: builder.ForPod("default", "my-pod").
ContainerStatuses(&v1.ContainerStatus{
Name: "container1",
State: v1.ContainerState{
Running: &v1.ContainerStateRunning{},
},
Ready: true,
}).
Result(),
hooks: []PodExecRestoreHook{
{
Hook: velerov1api.ExecRestoreHook{
WaitForReady: boolptr.True(),
},
},
},
},
{
name: "should return false when no state is set",
@@ -820,6 +865,7 @@ func TestIsContainerRunning(t *testing.T) {
State: v1.ContainerState{},
}).
Result(),
hooks: []PodExecRestoreHook{},
},
{
name: "should return false when waiting",
@@ -833,6 +879,7 @@ func TestIsContainerRunning(t *testing.T) {
},
}).
Result(),
hooks: []PodExecRestoreHook{},
},
{
name: "should return true when running and first container is terminated",
@@ -852,11 +899,12 @@ func TestIsContainerRunning(t *testing.T) {
},
}).
Result(),
hooks: []PodExecRestoreHook{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := isContainerRunning(test.pod, test.container)
actual := isContainerUp(test.pod, test.container, test.hooks)
assert.Equal(t, actual, test.expect)
})
}
@@ -85,6 +85,7 @@ func (p *Policies) buildPolicy(resPolicies *resourcePolicies) error {
volP.conditions = append(volP.conditions, &storageClassCondition{storageClass: con.StorageClass})
volP.conditions = append(volP.conditions, &nfsCondition{nfs: con.NFS})
volP.conditions = append(volP.conditions, &csiCondition{csi: con.CSI})
volP.conditions = append(volP.conditions, &volumeTypeCondition{volumeTypes: con.VolumeTypes})
p.volumePolicies = append(p.volumePolicies, volP)
}
@@ -370,6 +370,51 @@ volumePolicies:
},
skip: false,
},
{
name: "match volume by types",
yamlData: `version: v1
volumePolicies:
- conditions:
capacity: "0,100Gi"
volumeTypes:
- local
- hostPath
action:
type: skip`,
vol: &v1.PersistentVolume{
Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("1Gi"),
},
PersistentVolumeSource: v1.PersistentVolumeSource{
HostPath: &v1.HostPathVolumeSource{Path: "/mnt/data"},
},
},
},
skip: true,
},
{
name: "dismatch volume by types",
yamlData: `version: v1
volumePolicies:
- conditions:
capacity: "0,100Gi"
volumeTypes:
- local
action:
type: skip`,
vol: &v1.PersistentVolume{
Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("1Gi"),
},
PersistentVolumeSource: v1.PersistentVolumeSource{
HostPath: &v1.HostPathVolumeSource{Path: "/mnt/data"},
},
},
},
skip: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@@ -47,6 +47,7 @@ type structuredVolume struct {
storageClass string
nfs *nFSVolumeSource
csi *csiVolumeSource
volumeType SupportedVolume
}
func (s *structuredVolume) parsePV(pv *corev1api.PersistentVolume) {
@@ -61,6 +62,8 @@ func (s *structuredVolume) parsePV(pv *corev1api.PersistentVolume) {
if csi != nil {
s.csi = &csiVolumeSource{Driver: csi.Driver}
}
s.volumeType = getVolumeTypeFromPV(pv)
}
func (s *structuredVolume) parsePodVolume(vol *corev1api.Volume) {
@@ -73,6 +76,8 @@ func (s *structuredVolume) parsePodVolume(vol *corev1api.Volume) {
if csi != nil {
s.csi = &csiVolumeSource{Driver: csi.Driver}
}
s.volumeType = getVolumeTypeFromVolume(vol)
}
type capacityCondition struct {
@@ -38,10 +38,11 @@ type nFSVolumeSource struct {
// 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"`
Capacity string `yaml:"capacity,omitempty"`
StorageClass []string `yaml:"storageClass,omitempty"`
NFS *nFSVolumeSource `yaml:"nfs,omitempty"`
CSI *csiVolumeSource `yaml:"csi,omitempty"`
VolumeTypes []SupportedVolume `yaml:"volumeTypes,omitempty"`
}
func (c *capacityCondition) validate() error {
@@ -0,0 +1,247 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourcepolicies
import (
corev1api "k8s.io/api/core/v1"
)
type volumeTypeCondition struct {
volumeTypes []SupportedVolume
}
type SupportedVolume string
const (
AWSAzureDisk SupportedVolume = "awsAzureDisk"
AWSElasticBlockStore SupportedVolume = "awsElasticBlockStore"
AzureDisk SupportedVolume = "azureDisk"
AzureFile SupportedVolume = "azureFile"
Cinder SupportedVolume = "cinder"
CephFS SupportedVolume = "cephfs"
ConfigMap SupportedVolume = "configMap"
CSI SupportedVolume = "csi"
DownwardAPI SupportedVolume = "downwardAPI"
EmptyDir SupportedVolume = "emptyDir"
Ephemeral SupportedVolume = "ephemeral"
FC SupportedVolume = "fc"
Flocker SupportedVolume = "flocker"
FlexVolume SupportedVolume = "flexVolume"
GitRepo SupportedVolume = "gitRepo"
Glusterfs SupportedVolume = "glusterfs"
GCEPersistentDisk SupportedVolume = "gcePersistentDisk"
HostPath SupportedVolume = "hostPath"
ISCSI SupportedVolume = "iscsi"
Local SupportedVolume = "local"
NFS SupportedVolume = "nfs"
PhotonPersistentDisk SupportedVolume = "photonPersistentDisk"
PortworxVolume SupportedVolume = "portworxVolume"
Projected SupportedVolume = "projected"
Quobyte SupportedVolume = "quobyte"
RBD SupportedVolume = "rbd"
ScaleIO SupportedVolume = "scaleIO"
Secret SupportedVolume = "secret"
StorageOS SupportedVolume = "storageOS"
VsphereVolume SupportedVolume = "vsphereVolume"
)
func (v *volumeTypeCondition) match(s *structuredVolume) bool {
if len(v.volumeTypes) == 0 {
return true
}
for _, vt := range v.volumeTypes {
if vt == s.volumeType {
return true
}
}
return false
}
func (v *volumeTypeCondition) validate() error {
// validate by yamlv3
return nil
}
func getVolumeTypeFromPV(pv *corev1api.PersistentVolume) SupportedVolume {
if pv == nil {
return ""
}
if pv.Spec.AWSElasticBlockStore != nil {
return AWSElasticBlockStore
}
if pv.Spec.AzureDisk != nil {
return AzureDisk
}
if pv.Spec.AzureFile != nil {
return AzureFile
}
if pv.Spec.CephFS != nil {
return CephFS
}
if pv.Spec.Cinder != nil {
return Cinder
}
if pv.Spec.CSI != nil {
return CSI
}
if pv.Spec.FC != nil {
return FC
}
if pv.Spec.Flocker != nil {
return Flocker
}
if pv.Spec.FlexVolume != nil {
return FlexVolume
}
if pv.Spec.GCEPersistentDisk != nil {
return GCEPersistentDisk
}
if pv.Spec.Glusterfs != nil {
return Glusterfs
}
if pv.Spec.HostPath != nil {
return HostPath
}
if pv.Spec.ISCSI != nil {
return ISCSI
}
if pv.Spec.Local != nil {
return Local
}
if pv.Spec.NFS != nil {
return NFS
}
if pv.Spec.PhotonPersistentDisk != nil {
return PhotonPersistentDisk
}
if pv.Spec.PortworxVolume != nil {
return PortworxVolume
}
if pv.Spec.Quobyte != nil {
return Quobyte
}
if pv.Spec.RBD != nil {
return RBD
}
if pv.Spec.ScaleIO != nil {
return ScaleIO
}
if pv.Spec.StorageOS != nil {
return StorageOS
}
if pv.Spec.VsphereVolume != nil {
return VsphereVolume
}
return ""
}
func getVolumeTypeFromVolume(vol *corev1api.Volume) SupportedVolume {
if vol == nil {
return ""
}
if vol.AWSElasticBlockStore != nil {
return AWSElasticBlockStore
}
if vol.AzureDisk != nil {
return AzureDisk
}
if vol.AzureFile != nil {
return AzureFile
}
if vol.CephFS != nil {
return CephFS
}
if vol.Cinder != nil {
return Cinder
}
if vol.CSI != nil {
return CSI
}
if vol.FC != nil {
return FC
}
if vol.Flocker != nil {
return Flocker
}
if vol.FlexVolume != nil {
return FlexVolume
}
if vol.GCEPersistentDisk != nil {
return GCEPersistentDisk
}
if vol.GitRepo != nil {
return GitRepo
}
if vol.Glusterfs != nil {
return Glusterfs
}
if vol.ISCSI != nil {
return ISCSI
}
if vol.NFS != nil {
return NFS
}
if vol.Secret != nil {
return Secret
}
if vol.RBD != nil {
return RBD
}
if vol.DownwardAPI != nil {
return DownwardAPI
}
if vol.ConfigMap != nil {
return ConfigMap
}
if vol.Projected != nil {
return Projected
}
if vol.Ephemeral != nil {
return Ephemeral
}
if vol.FC != nil {
return FC
}
if vol.PhotonPersistentDisk != nil {
return PhotonPersistentDisk
}
if vol.PortworxVolume != nil {
return PortworxVolume
}
if vol.Quobyte != nil {
return Quobyte
}
if vol.ScaleIO != nil {
return ScaleIO
}
if vol.StorageOS != nil {
return StorageOS
}
if vol.VsphereVolume != nil {
return VsphereVolume
}
if vol.HostPath != nil {
return HostPath
}
if vol.EmptyDir != nil {
return EmptyDir
}
return ""
}
@@ -0,0 +1,576 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourcepolicies
import (
"testing"
corev1api "k8s.io/api/core/v1"
)
func TestGetVolumeTypeFromPV(t *testing.T) {
testCases := []struct {
name string
inputPV *corev1api.PersistentVolume
expected SupportedVolume
}{
{
name: "nil PersistentVolume",
inputPV: nil,
expected: "",
},
{
name: "Test GCEPersistentDisk",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
GCEPersistentDisk: &corev1api.GCEPersistentDiskVolumeSource{},
},
},
},
expected: GCEPersistentDisk,
},
{
name: "Test AWSElasticBlockStore",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
AWSElasticBlockStore: &corev1api.AWSElasticBlockStoreVolumeSource{},
},
},
},
expected: AWSElasticBlockStore,
},
{
name: "Test HostPath",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
HostPath: &corev1api.HostPathVolumeSource{},
},
},
},
expected: HostPath,
},
{
name: "Test Glusterfs",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
Glusterfs: &corev1api.GlusterfsPersistentVolumeSource{},
},
},
},
expected: Glusterfs,
},
{
name: "Test NFS",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
NFS: &corev1api.NFSVolumeSource{},
},
},
},
expected: NFS,
},
{
name: "Test RBD",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
RBD: &corev1api.RBDPersistentVolumeSource{},
},
},
},
expected: RBD,
},
{
name: "Test ISCSI",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
ISCSI: &corev1api.ISCSIPersistentVolumeSource{},
},
},
},
expected: ISCSI,
},
{
name: "Test Cinder",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
Cinder: &corev1api.CinderPersistentVolumeSource{},
},
},
},
expected: Cinder,
},
{
name: "Test CephFS",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
CephFS: &corev1api.CephFSPersistentVolumeSource{},
},
},
},
expected: CephFS,
},
{
name: "Test FC",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
FC: &corev1api.FCVolumeSource{},
},
},
},
expected: FC,
},
{
name: "Test Flocker",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
Flocker: &corev1api.FlockerVolumeSource{},
},
},
},
expected: Flocker,
},
{
name: "Test FlexVolume",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
FlexVolume: &corev1api.FlexPersistentVolumeSource{},
},
},
},
expected: FlexVolume,
},
{
name: "Test AzureFile",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
AzureFile: &corev1api.AzureFilePersistentVolumeSource{},
},
},
},
expected: AzureFile,
},
{
name: "Test VsphereVolume",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
VsphereVolume: &corev1api.VsphereVirtualDiskVolumeSource{},
},
},
},
expected: VsphereVolume,
},
{
name: "Test Quobyte",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
Quobyte: &corev1api.QuobyteVolumeSource{},
},
},
},
expected: Quobyte,
},
{
name: "Test AzureDisk",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
AzureDisk: &corev1api.AzureDiskVolumeSource{},
},
},
},
expected: AzureDisk,
},
{
name: "Test PhotonPersistentDisk",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
PhotonPersistentDisk: &corev1api.PhotonPersistentDiskVolumeSource{},
},
},
},
expected: PhotonPersistentDisk,
},
{
name: "Test PortworxVolume",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
PortworxVolume: &corev1api.PortworxVolumeSource{},
},
},
},
expected: PortworxVolume,
},
{
name: "Test ScaleIO",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
ScaleIO: &corev1api.ScaleIOPersistentVolumeSource{},
},
},
},
expected: ScaleIO,
},
{
name: "Test Local",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
Local: &corev1api.LocalVolumeSource{},
},
},
},
expected: Local,
},
{
name: "Test StorageOS",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
StorageOS: &corev1api.StorageOSPersistentVolumeSource{},
},
},
},
expected: StorageOS,
},
{
name: "Test CSI",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{
PersistentVolumeSource: corev1api.PersistentVolumeSource{
CSI: &corev1api.CSIPersistentVolumeSource{},
},
},
},
expected: CSI,
},
{
name: "Test Unknown Source",
inputPV: &corev1api.PersistentVolume{
Spec: corev1api.PersistentVolumeSpec{},
},
expected: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := getVolumeTypeFromPV(tc.inputPV)
if result != tc.expected {
t.Errorf("Expected %s, but got %s", tc.expected, result)
}
})
}
}
func TestGetVolumeTypeFromVolume(t *testing.T) {
testCases := []struct {
name string
inputVol *corev1api.Volume
expected SupportedVolume
}{
{
name: "nil Volume",
inputVol: nil,
expected: "",
},
{
name: "Test Unknown Source",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{},
},
expected: "",
},
{
name: "Test HostPath",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
HostPath: &corev1api.HostPathVolumeSource{},
},
},
expected: HostPath,
},
{
name: "Test EmptyDir",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
EmptyDir: &corev1api.EmptyDirVolumeSource{},
},
},
expected: EmptyDir,
},
{
name: "Test GCEPersistentDisk",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
GCEPersistentDisk: &corev1api.GCEPersistentDiskVolumeSource{},
},
},
expected: GCEPersistentDisk,
},
{
name: "Test AWSElasticBlockStore",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
AWSElasticBlockStore: &corev1api.AWSElasticBlockStoreVolumeSource{},
},
},
expected: AWSElasticBlockStore,
},
{
name: "Test GitRepo",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
GitRepo: &corev1api.GitRepoVolumeSource{},
},
},
expected: GitRepo,
},
{
name: "Test Secret",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
Secret: &corev1api.SecretVolumeSource{},
},
},
expected: Secret,
},
{
name: "Test NFS",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
NFS: &corev1api.NFSVolumeSource{},
},
},
expected: NFS,
},
{
name: "Test ISCSI",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
ISCSI: &corev1api.ISCSIVolumeSource{},
},
},
expected: ISCSI,
},
{
name: "Test Glusterfs",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
Glusterfs: &corev1api.GlusterfsVolumeSource{},
},
},
expected: Glusterfs,
},
{
name: "Test RBD",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
RBD: &corev1api.RBDVolumeSource{},
},
},
expected: RBD,
},
{
name: "Test FlexVolume",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
FlexVolume: &corev1api.FlexVolumeSource{},
},
},
expected: FlexVolume,
},
{
name: "Test Cinder",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
Cinder: &corev1api.CinderVolumeSource{},
},
},
expected: Cinder,
},
{
name: "Test CephFS",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
CephFS: &corev1api.CephFSVolumeSource{},
},
},
expected: CephFS,
},
{
name: "Test Flocker",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
Flocker: &corev1api.FlockerVolumeSource{},
},
},
expected: Flocker,
},
{
name: "Test DownwardAPI",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
DownwardAPI: &corev1api.DownwardAPIVolumeSource{},
},
},
expected: DownwardAPI,
},
{
name: "Test FC",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
FC: &corev1api.FCVolumeSource{},
},
},
expected: FC,
},
{
name: "Test AzureFile",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
AzureFile: &corev1api.AzureFileVolumeSource{},
},
},
expected: AzureFile,
},
{
name: "Test ConfigMap",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
ConfigMap: &corev1api.ConfigMapVolumeSource{},
},
},
expected: ConfigMap,
},
{
name: "Test VsphereVolume",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
VsphereVolume: &corev1api.VsphereVirtualDiskVolumeSource{},
},
},
expected: VsphereVolume,
},
{
name: "Test Quobyte",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
Quobyte: &corev1api.QuobyteVolumeSource{},
},
},
expected: Quobyte,
},
{
name: "Test AzureDisk",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
AzureDisk: &corev1api.AzureDiskVolumeSource{},
},
},
expected: AzureDisk,
},
{
name: "Test PhotonPersistentDisk",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
PhotonPersistentDisk: &corev1api.PhotonPersistentDiskVolumeSource{},
},
},
expected: PhotonPersistentDisk,
},
{
name: "Test Projected",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
Projected: &corev1api.ProjectedVolumeSource{},
},
},
expected: Projected,
},
{
name: "Test PortworxVolume",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
PortworxVolume: &corev1api.PortworxVolumeSource{},
},
},
expected: PortworxVolume,
},
{
name: "Test ScaleIO",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
ScaleIO: &corev1api.ScaleIOVolumeSource{},
},
},
expected: ScaleIO,
},
{
name: "Test StorageOS",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
StorageOS: &corev1api.StorageOSVolumeSource{},
},
},
expected: StorageOS,
},
{
name: "Test CSI",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
CSI: &corev1api.CSIVolumeSource{},
},
},
expected: CSI,
},
{
name: "Test Ephemeral",
inputVol: &corev1api.Volume{
VolumeSource: corev1api.VolumeSource{
Ephemeral: &corev1api.EphemeralVolumeSource{},
},
},
expected: Ephemeral,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := getVolumeTypeFromVolume(tc.inputVol)
if result != tc.expected {
t.Errorf("Expected %s, but got %s", tc.expected, result)
}
})
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
[build]
base = "site/"
command = "hugo --gc --minify"
publish = "public"
publish = "site/public"
[context.production.environment]
HUGO_VERSION = "0.73.0"
+5
View File
@@ -214,6 +214,11 @@ type ExecRestoreHook struct {
// before attempting to run the command.
// +optional
WaitTimeout metav1.Duration `json:"waitTimeout,omitempty"`
// WaitForReady ensures command will be launched when container is Ready instead of Running.
// +optional
// +nullable
WaitForReady *bool `json:"waitForReady,omitempty"`
}
// InitRestoreHook is a hook that adds an init container to a PodSpec to run commands before the
+7
View File
@@ -46,6 +46,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/builder"
"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"
@@ -1379,6 +1380,12 @@ func TestBackupItemActionsForSkippedPV(t *testing.T) {
expectNotSkippedPVs: []string{"pv-1"},
},
}
// Enable CSI feature before running the test, because Velero will check whether
// CSI feature is enabled before executing CSI plugin actions.
features.NewFeatureFlagSet("EnableCSI")
defer func() {
features.NewFeatureFlagSet("")
}()
for _, tc := range tests {
t.Run(tc.name, func(tt *testing.T) {
var (
+10
View File
@@ -52,6 +52,7 @@ import (
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"
csiutil "github.com/vmware-tanzu/velero/pkg/util/csi"
pdvolumeutil "github.com/vmware-tanzu/velero/pkg/util/podvolume"
"github.com/vmware-tanzu/velero/pkg/volume"
)
@@ -361,6 +362,14 @@ func (ib *itemBackupper) executeActions(
ib.trackSkippedPV(obj, groupResource, "", "skipped due to resource policy ", log)
continue
}
// If the EnableCSI feature is not enabled, but the executing action is from CSI plugin, skip the action.
if csiutil.ShouldSkipAction(actionName) {
log.Infof("Skip action %s for resource %s:%s/%s, because the CSI feature is not enabled. Feature setting is %s.",
actionName, groupResource.String(), metadata.GetNamespace(), metadata.GetName(), features.Serialize())
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)
@@ -652,6 +661,7 @@ func (ib *itemBackupper) getMatchAction(obj runtime.Unstructured, groupResource
}
return ib.backupRequest.ResPolicies.GetMatchAction(pv)
}
return nil, nil
}
@@ -1,3 +1,19 @@
/*
Copyright 2023 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builder
import (
+16
View File
@@ -1,3 +1,19 @@
/*
Copyright 2023 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builder
import (
+16
View File
@@ -1,3 +1,19 @@
/*
Copyright 2023 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builder
import corev1api "k8s.io/api/core/v1"
+16
View File
@@ -1,3 +1,19 @@
/*
Copyright 2023 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builder
import (
+21
View File
@@ -0,0 +1,21 @@
/*
Copyright 2017 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
// Make sure we import the client-go auth provider plugins.
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
)
+8
View File
@@ -18,6 +18,7 @@ package client
import (
"context"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -25,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/dynamicinformer"
)
// DynamicFactory contains methods for retrieving dynamic clients for GroupVersionResources and
@@ -33,6 +35,8 @@ type DynamicFactory interface {
// ClientForGroupVersionResource returns a Dynamic client for the given group/version
// and resource for the given namespace.
ClientForGroupVersionResource(gv schema.GroupVersion, resource metav1.APIResource, namespace string) (Dynamic, error)
// DynamicSharedInformerFactoryForNamespace returns a DynamicSharedInformerFactory for the given namespace.
DynamicSharedInformerFactoryForNamespace(namespace string) dynamicinformer.DynamicSharedInformerFactory
}
// dynamicFactory implements DynamicFactory.
@@ -51,6 +55,10 @@ func (f *dynamicFactory) ClientForGroupVersionResource(gv schema.GroupVersion, r
}, nil
}
func (f *dynamicFactory) DynamicSharedInformerFactoryForNamespace(namespace string) dynamicinformer.DynamicSharedInformerFactory {
return dynamicinformer.NewFilteredDynamicSharedInformerFactory(f.dynamicClient, time.Minute, namespace, nil)
}
// Creator creates an object.
type Creator interface {
// Create creates an object.
+4
View File
@@ -81,6 +81,7 @@ type Options struct {
DefaultVolumesToFsBackup bool
UploaderType string
DefaultSnapshotMoveData bool
DisableInformerCache bool
}
// BindFlags adds command line values to the options struct.
@@ -122,6 +123,7 @@ func (o *Options) BindFlags(flags *pflag.FlagSet) {
flags.BoolVar(&o.DefaultVolumesToFsBackup, "default-volumes-to-fs-backup", o.DefaultVolumesToFsBackup, "Bool flag to configure Velero server to use pod volume file system backup by default for all volumes on all backups. Optional.")
flags.StringVar(&o.UploaderType, "uploader-type", o.UploaderType, fmt.Sprintf("The type of uploader to transfer the data of pod volumes, the supported values are '%s', '%s'", uploader.ResticType, uploader.KopiaType))
flags.BoolVar(&o.DefaultSnapshotMoveData, "default-snapshot-move-data", o.DefaultSnapshotMoveData, "Bool flag to configure Velero server to move data by default for all snapshots supporting data movement. Optional.")
flags.BoolVar(&o.DisableInformerCache, "disable-informer-cache", o.DisableInformerCache, "Disable informer cache for Get calls on restore. With this enabled, it will speed up restore in cases where there are backup resources which already exist in the cluster, but for very large clusters this will increase velero memory usage. Default is false (don't disable). Optional.")
}
// NewInstallOptions instantiates a new, default InstallOptions struct.
@@ -149,6 +151,7 @@ func NewInstallOptions() *Options {
DefaultVolumesToFsBackup: false,
UploaderType: uploader.KopiaType,
DefaultSnapshotMoveData: false,
DisableInformerCache: true,
}
}
@@ -213,6 +216,7 @@ func (o *Options) AsVeleroOptions() (*install.VeleroOptions, error) {
DefaultVolumesToFsBackup: o.DefaultVolumesToFsBackup,
UploaderType: o.UploaderType,
DefaultSnapshotMoveData: o.DefaultSnapshotMoveData,
DisableInformerCache: o.DisableInformerCache,
}, nil
}
+10 -8
View File
@@ -121,6 +121,7 @@ func NewServerCommand(f client.Factory) *cobra.Command {
command.Flags().Var(formatFlag, "log-format", fmt.Sprintf("The format for log output. Valid values are %s.", strings.Join(formatFlag.AllowedValues(), ", ")))
command.Flags().DurationVar(&config.resourceTimeout, "resource-timeout", config.resourceTimeout, "How long to wait for resource processes which are not covered by other specific timeout parameters. Default is 10 minutes.")
command.Flags().DurationVar(&config.dataMoverPrepareTimeout, "data-mover-prepare-timeout", config.dataMoverPrepareTimeout, "How long to wait for preparing a DataUpload/DataDownload. Default is 30 minutes.")
command.Flags().StringVar(&config.metricsAddress, "metrics-address", config.metricsAddress, "The address to expose prometheus metrics")
command.Flags().IntVar(&config.dataPathConcurrentNum, "data-path-concurrent-num", config.dataPathConcurrentNum, "The concurrent number of data path in the current node. Default is 1.")
return command
@@ -202,14 +203,15 @@ func newNodeAgentServer(logger logrus.FieldLogger, factory client.Factory, confi
}
s := &nodeAgentServer{
logger: logger,
ctx: ctx,
cancelFunc: cancelFunc,
fileSystem: filesystem.NewFileSystem(),
mgr: mgr,
config: config,
namespace: factory.Namespace(),
nodeName: nodeName,
logger: logger,
ctx: ctx,
cancelFunc: cancelFunc,
fileSystem: filesystem.NewFileSystem(),
mgr: mgr,
config: config,
namespace: factory.Namespace(),
nodeName: nodeName,
metricsAddress: config.metricsAddress,
}
// the cache isn't initialized yet when "validatePodVolumesHostPath" is called, the client returned by the manager cannot
+14 -1
View File
@@ -73,6 +73,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/persistence"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
"github.com/vmware-tanzu/velero/pkg/podexec"
"github.com/vmware-tanzu/velero/pkg/podvolume"
"github.com/vmware-tanzu/velero/pkg/repository"
@@ -112,6 +113,7 @@ const (
defaultCredentialsDirectory = "/tmp/credentials"
defaultMaxConcurrentK8SConnections = 30
defaultDisableInformerCache = false
)
type serverConfig struct {
@@ -136,6 +138,7 @@ type serverConfig struct {
uploaderType string
maxConcurrentK8SConnections int
defaultSnapshotMoveData bool
disableInformerCache bool
}
func NewCommand(f client.Factory) *cobra.Command {
@@ -165,6 +168,7 @@ func NewCommand(f client.Factory) *cobra.Command {
uploaderType: uploader.ResticType,
maxConcurrentK8SConnections: defaultMaxConcurrentK8SConnections,
defaultSnapshotMoveData: false,
disableInformerCache: defaultDisableInformerCache,
}
)
@@ -236,6 +240,7 @@ func NewCommand(f client.Factory) *cobra.Command {
command.Flags().DurationVar(&config.resourceTimeout, "resource-timeout", config.resourceTimeout, "How long to wait for resource processes which are not covered by other specific timeout parameters. Default is 10 minutes.")
command.Flags().IntVar(&config.maxConcurrentK8SConnections, "max-concurrent-k8s-connections", config.maxConcurrentK8SConnections, "Max concurrent connections number that Velero can create with kube-apiserver. Default is 30.")
command.Flags().BoolVar(&config.defaultSnapshotMoveData, "default-snapshot-move-data", config.defaultSnapshotMoveData, "Move data by default for all snapshots supporting data movement.")
command.Flags().BoolVar(&config.disableInformerCache, "disable-informer-cache", config.disableInformerCache, "Disable informer cache for Get calls on restore. With this enabled, it will speed up restore in cases where there are backup resources which already exist in the cluster, but for very large clusters this will increase velero memory usage. Default is false (don't disable).")
return command
}
@@ -305,6 +310,13 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
return nil, err
}
if !features.IsEnabled(velerov1api.CSIFeatureFlag) {
_, err = pluginRegistry.Get(common.PluginKindBackupItemActionV2, "velero.io/csi-pvc-backupper")
if err == nil {
logger.Warn("CSI plugins are registered, but the EnableCSI feature is not enabled.")
}
}
// cancelFunc is not deferred here because if it was, then ctx would immediately
// be canceled once this function exited, making it useless to any informers using later.
// That, in turn, causes the velero server to halt when the first informer tries to use it.
@@ -936,6 +948,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.metrics,
s.config.formatFlag.Parse(),
s.config.defaultItemOperationTimeout,
s.config.disableInformerCache,
)
if err = r.SetupWithManager(s.mgr); err != nil {
@@ -1146,7 +1159,7 @@ func markDataDownloadsCancel(ctx context.Context, client ctrlclient.Client, rest
})
if err != nil {
log.WithError(errors.WithStack(err)).Errorf("failed to mark dataupload %q cancel", dd.GetName())
log.WithError(errors.WithStack(err)).Errorf("failed to mark datadownload %q cancel", dd.GetName())
continue
}
log.WithField("datadownload", dd.GetName()).Warn(dd.Status.Message)
+2 -1
View File
@@ -21,6 +21,7 @@ import (
"context"
"fmt"
"os"
"strings"
"time"
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
@@ -476,7 +477,7 @@ func (b *backupReconciler) prepareBackupRequest(backup *velerov1api.Backup, logg
request.Status.ValidationErrors = append(request.Status.ValidationErrors, "encountered labelSelector as well as orLabelSelectors in backup spec, only one can be specified")
}
if request.Spec.ResourcePolicy != nil && request.Spec.ResourcePolicy.Kind == resourcepolicies.ConfigmapRefType {
if request.Spec.ResourcePolicy != nil && strings.EqualFold(request.Spec.ResourcePolicy.Kind, resourcepolicies.ConfigmapRefType) {
policiesConfigmap := &corev1api.ConfigMap{}
err := b.kbClient.Get(context.Background(), kbclient.ObjectKey{Namespace: request.Namespace, Name: request.Spec.ResourcePolicy.Name}, policiesConfigmap)
if err != nil {
+2 -2
View File
@@ -287,12 +287,13 @@ func (r *DataDownloadReconciler) Reconcile(ctx context.Context, req ctrl.Request
} else if dd.Status.Phase == velerov2alpha1api.DataDownloadPhaseInProgress {
log.Info("Data download is in progress")
if dd.Spec.Cancel {
log.Info("Data download is being canceled")
fsRestore := r.dataPathMgr.GetAsyncBR(dd.Name)
if fsRestore == nil {
r.OnDataDownloadCancelled(ctx, dd.GetNamespace(), dd.GetName())
return ctrl.Result{}, nil
}
log.Info("Data download is being canceled")
// Update status to Canceling.
original := dd.DeepCopy()
dd.Status.Phase = velerov2alpha1api.DataDownloadPhaseCanceling
@@ -300,7 +301,6 @@ func (r *DataDownloadReconciler) Reconcile(ctx context.Context, req ctrl.Request
log.WithError(err).Error("error updating data download status")
return ctrl.Result{}, err
}
fsRestore.Cancel()
return ctrl.Result{}, nil
}
+10 -6
View File
@@ -292,13 +292,15 @@ func (r *DataUploadReconciler) Reconcile(ctx context.Context, req ctrl.Request)
} else if du.Status.Phase == velerov2alpha1api.DataUploadPhaseInProgress {
log.Info("Data upload is in progress")
if du.Spec.Cancel {
fsBackup := r.dataPathMgr.GetAsyncBR(du.Name)
if fsBackup == nil {
return ctrl.Result{}, nil
}
log.Info("Data upload is being canceled")
// Update status to Canceling.
fsBackup := r.dataPathMgr.GetAsyncBR(du.Name)
if fsBackup == nil {
r.OnDataUploadCancelled(ctx, du.GetNamespace(), du.GetName())
return ctrl.Result{}, nil
}
// Update status to Canceling
original := du.DeepCopy()
du.Status.Phase = velerov2alpha1api.DataUploadPhaseCanceling
if err := r.client.Patch(ctx, du, client.MergeFrom(original)); err != nil {
@@ -761,7 +763,9 @@ func (r *DataUploadReconciler) setupExposeParam(du *velerov2alpha1api.DataUpload
StorageClass: du.Spec.CSISnapshot.StorageClass,
HostingPodLabels: map[string]string{velerov1api.DataUploadLabel: du.Name},
AccessMode: accessMode,
Timeout: du.Spec.OperationTimeout.Duration,
OperationTimeout: du.Spec.OperationTimeout.Duration,
ExposeTimeout: r.preparingTimeout,
VolumeSize: pvc.Spec.Resources.Requests[corev1.ResourceStorage],
}, nil
}
return nil, nil
+13 -8
View File
@@ -25,6 +25,7 @@ import (
"io"
"os"
"sort"
"strings"
"time"
"github.com/pkg/errors"
@@ -101,6 +102,7 @@ type restoreReconciler struct {
logFormat logging.Format
clock clock.WithTickerAndDelayedExecution
defaultItemOperationTimeout time.Duration
disableInformerCache bool
newPluginManager func(logger logrus.FieldLogger) clientmgmt.Manager
backupStoreGetter persistence.ObjectBackupStoreGetter
@@ -123,6 +125,7 @@ func NewRestoreReconciler(
metrics *metrics.ServerMetrics,
logFormat logging.Format,
defaultItemOperationTimeout time.Duration,
disableInformerCache bool,
) *restoreReconciler {
r := &restoreReconciler{
ctx: ctx,
@@ -135,6 +138,7 @@ func NewRestoreReconciler(
logFormat: logFormat,
clock: &clock.RealClock{},
defaultItemOperationTimeout: defaultItemOperationTimeout,
disableInformerCache: disableInformerCache,
// use variables to refer to these functions so they can be
// replaced with fakes for testing.
@@ -373,7 +377,7 @@ func (r *restoreReconciler) validateAndComplete(restore *api.Restore) (backupInf
}
var resourceModifiers *resourcemodifiers.ResourceModifiers = nil
if restore.Spec.ResourceModifier != nil && restore.Spec.ResourceModifier.Kind == resourcemodifiers.ConfigmapRefType {
if restore.Spec.ResourceModifier != nil && strings.EqualFold(restore.Spec.ResourceModifier.Kind, resourcemodifiers.ConfigmapRefType) {
ResourceModifierConfigMap := &corev1api.ConfigMap{}
err := r.kbClient.Get(context.Background(), client.ObjectKey{Namespace: restore.Namespace, Name: restore.Spec.ResourceModifier.Name}, ResourceModifierConfigMap)
if err != nil {
@@ -519,13 +523,14 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
}
restoreReq := &pkgrestore.Request{
Log: restoreLog,
Restore: restore,
Backup: info.backup,
PodVolumeBackups: podVolumeBackups,
VolumeSnapshots: volumeSnapshots,
BackupReader: backupFile,
ResourceModifiers: resourceModifiers,
Log: restoreLog,
Restore: restore,
Backup: info.backup,
PodVolumeBackups: podVolumeBackups,
VolumeSnapshots: volumeSnapshots,
BackupReader: backupFile,
ResourceModifiers: resourceModifiers,
DisableInformerCache: r.disableInformerCache,
}
restoreWarnings, restoreErrors := r.restorer.RestoreWithResolvers(restoreReq, actionsResolver, pluginManager)
+7 -1
View File
@@ -114,6 +114,7 @@ func TestFetchBackupInfo(t *testing.T) {
metrics.NewServerMetrics(),
formatFlag,
60*time.Minute,
false,
)
if test.backupStoreError == nil {
@@ -191,6 +192,7 @@ func TestProcessQueueItemSkips(t *testing.T) {
metrics.NewServerMetrics(),
formatFlag,
60*time.Minute,
false,
)
_, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{
@@ -445,6 +447,7 @@ func TestRestoreReconcile(t *testing.T) {
metrics.NewServerMetrics(),
formatFlag,
60*time.Minute,
false,
)
r.clock = clocktesting.NewFakeClock(now)
@@ -616,6 +619,7 @@ func TestValidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
metrics.NewServerMetrics(),
formatFlag,
60*time.Minute,
false,
)
restore := &velerov1api.Restore{
@@ -708,6 +712,7 @@ func TestValidateAndCompleteWithResourceModifierSpecified(t *testing.T) {
metrics.NewServerMetrics(),
formatFlag,
60*time.Minute,
false,
)
restore := &velerov1api.Restore{
@@ -776,7 +781,8 @@ func TestValidateAndCompleteWithResourceModifierSpecified(t *testing.T) {
Spec: velerov1api.RestoreSpec{
BackupName: "backup-1",
ResourceModifier: &corev1.TypedLocalObjectReference{
Kind: resourcemodifiers.ConfigmapRefType,
// intentional to ensure case insensitivity works as expected
Kind: "confIGMaP",
Name: "test-configmap-invalid",
},
},
+20 -6
View File
@@ -59,8 +59,14 @@ type CSISnapshotExposeParam struct {
// HostingPodLabels is the labels that are going to apply to the hosting pod
HostingPodLabels map[string]string
// Timeout specifies the time wait for resources operations in Expose
Timeout time.Duration
// OperationTimeout specifies the time wait for resources operations in Expose
OperationTimeout time.Duration
// ExposeTimeout specifies the timeout for the entire expose process
ExposeTimeout time.Duration
// VolumeSize specifies the size of the source volume
VolumeSize resource.Quantity
}
// CSISnapshotExposeWaitParam define the input param for WaitExposed of CSI snapshots
@@ -94,7 +100,7 @@ func (e *csiSnapshotExposer) Expose(ctx context.Context, ownerObject corev1.Obje
curLog.Info("Exposing CSI snapshot")
volumeSnapshot, err := csi.WaitVolumeSnapshotReady(ctx, e.csiSnapshotClient, csiExposeParam.SnapshotName, csiExposeParam.SourceNamespace, csiExposeParam.Timeout, curLog)
volumeSnapshot, err := csi.WaitVolumeSnapshotReady(ctx, e.csiSnapshotClient, csiExposeParam.SnapshotName, csiExposeParam.SourceNamespace, csiExposeParam.ExposeTimeout, curLog)
if err != nil {
return errors.Wrapf(err, "error wait volume snapshot ready")
}
@@ -121,14 +127,14 @@ func (e *csiSnapshotExposer) Expose(ctx context.Context, ownerObject corev1.Obje
}
}()
err = csi.EnsureDeleteVS(ctx, e.csiSnapshotClient, volumeSnapshot.Name, volumeSnapshot.Namespace, csiExposeParam.Timeout)
err = csi.EnsureDeleteVS(ctx, e.csiSnapshotClient, volumeSnapshot.Name, volumeSnapshot.Namespace, csiExposeParam.OperationTimeout)
if err != nil {
return errors.Wrap(err, "error to delete volume snapshot")
}
curLog.WithField("vs name", volumeSnapshot.Name).Infof("VS is deleted in namespace %s", volumeSnapshot.Namespace)
err = csi.EnsureDeleteVSC(ctx, e.csiSnapshotClient, vsc.Name, csiExposeParam.Timeout)
err = csi.EnsureDeleteVSC(ctx, e.csiSnapshotClient, vsc.Name, csiExposeParam.OperationTimeout)
if err != nil {
return errors.Wrap(err, "error to delete volume snapshot content")
}
@@ -156,7 +162,15 @@ func (e *csiSnapshotExposer) Expose(ctx context.Context, ownerObject corev1.Obje
curLog.WithField("vsc name", backupVSC.Name).Infof("Backup VSC is created from %s", vsc.Name)
backupPVC, err := e.createBackupPVC(ctx, ownerObject, backupVS.Name, csiExposeParam.StorageClass, csiExposeParam.AccessMode, *volumeSnapshot.Status.RestoreSize)
var volumeSize resource.Quantity
if volumeSnapshot.Status.RestoreSize != nil && !volumeSnapshot.Status.RestoreSize.IsZero() {
volumeSize = *volumeSnapshot.Status.RestoreSize
} else {
volumeSize = csiExposeParam.VolumeSize
curLog.WithField("vs name", volumeSnapshot.Name).Warnf("The snapshot doesn't contain a valid restore size, use source volume's size %v", volumeSize)
}
backupPVC, err := e.createBackupPVC(ctx, ownerObject, backupVS.Name, csiExposeParam.StorageClass, csiExposeParam.AccessMode, volumeSize)
if err != nil {
return errors.Wrap(err, "error to create backup pvc")
}
+98 -40
View File
@@ -59,6 +59,8 @@ func TestExpose(t *testing.T) {
},
}
var restoreSize int64 = 123456
snapshotClass := "fake-snapshot-class"
vsObject := &snapshotv1api.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
@@ -78,11 +80,31 @@ func TestExpose(t *testing.T) {
Status: &snapshotv1api.VolumeSnapshotStatus{
BoundVolumeSnapshotContentName: &vscName,
ReadyToUse: boolptr.True(),
RestoreSize: &resource.Quantity{},
RestoreSize: resource.NewQuantity(restoreSize, ""),
},
}
vsObjectWithoutRestoreSize := &snapshotv1api.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "fake-vs",
Namespace: "fake-ns",
Annotations: map[string]string{
"fake-key-1": "fake-value-1",
"fake-key-2": "fake-value-2",
},
},
Spec: snapshotv1api.VolumeSnapshotSpec{
Source: snapshotv1api.VolumeSnapshotSource{
VolumeSnapshotContentName: &vscName,
},
VolumeSnapshotClassName: &snapshotClass,
},
Status: &snapshotv1api.VolumeSnapshotStatus{
BoundVolumeSnapshotContentName: &vscName,
ReadyToUse: boolptr.True(),
},
}
var restoreSize int64
snapshotHandle := "fake-handle"
vscObj := &snapshotv1api.VolumeSnapshotContent{
ObjectMeta: metav1.ObjectMeta{
@@ -116,21 +138,23 @@ func TestExpose(t *testing.T) {
}
tests := []struct {
name string
snapshotClientObj []runtime.Object
kubeClientObj []runtime.Object
ownerBackup *velerov1.Backup
exposeParam CSISnapshotExposeParam
snapReactors []reactor
kubeReactors []reactor
err string
name string
snapshotClientObj []runtime.Object
kubeClientObj []runtime.Object
ownerBackup *velerov1.Backup
exposeParam CSISnapshotExposeParam
snapReactors []reactor
kubeReactors []reactor
err string
expectedVolumeSize *resource.Quantity
}{
{
name: "wait vs ready fail",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
Timeout: time.Millisecond,
SnapshotName: "fake-vs",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
},
err: "error wait volume snapshot ready: error to get volumesnapshot /fake-vs: volumesnapshots.snapshot.storage.k8s.io \"fake-vs\" not found",
},
@@ -138,9 +162,10 @@ func TestExpose(t *testing.T) {
name: "get vsc fail",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
Timeout: time.Millisecond,
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -151,9 +176,10 @@ func TestExpose(t *testing.T) {
name: "delete vs fail",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
Timeout: time.Millisecond,
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -174,9 +200,10 @@ func TestExpose(t *testing.T) {
name: "delete vsc fail",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
Timeout: time.Millisecond,
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -197,9 +224,10 @@ func TestExpose(t *testing.T) {
name: "create backup vs fail",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
Timeout: time.Millisecond,
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -220,9 +248,10 @@ func TestExpose(t *testing.T) {
name: "create backup vsc fail",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
Timeout: time.Millisecond,
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -257,10 +286,11 @@ func TestExpose(t *testing.T) {
name: "create backup pvc fail",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
Timeout: time.Millisecond,
AccessMode: AccessModeFileSystem,
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
AccessMode: AccessModeFileSystem,
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -281,10 +311,11 @@ func TestExpose(t *testing.T) {
name: "create backup pod fail",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
AccessMode: AccessModeFileSystem,
Timeout: time.Millisecond,
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -308,10 +339,11 @@ func TestExpose(t *testing.T) {
name: "success",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
AccessMode: AccessModeFileSystem,
Timeout: time.Millisecond,
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
},
snapshotClientObj: []runtime.Object{
vsObject,
@@ -321,6 +353,26 @@ func TestExpose(t *testing.T) {
daemonSet,
},
},
{
name: "restore size from exposeParam",
ownerBackup: backup,
exposeParam: CSISnapshotExposeParam{
SnapshotName: "fake-vs",
SourceNamespace: "fake-ns",
AccessMode: AccessModeFileSystem,
OperationTimeout: time.Millisecond,
ExposeTimeout: time.Millisecond,
VolumeSize: *resource.NewQuantity(567890, ""),
},
snapshotClientObj: []runtime.Object{
vsObjectWithoutRestoreSize,
vscObj,
},
kubeClientObj: []runtime.Object{
daemonSet,
},
expectedVolumeSize: resource.NewQuantity(567890, ""),
},
}
for _, test := range tests {
@@ -360,7 +412,7 @@ func TestExpose(t *testing.T) {
_, err = exposer.kubeClient.CoreV1().Pods(ownerObject.Namespace).Get(context.Background(), ownerObject.Name, metav1.GetOptions{})
assert.NoError(t, err)
_, err = exposer.kubeClient.CoreV1().PersistentVolumeClaims(ownerObject.Namespace).Get(context.Background(), ownerObject.Name, metav1.GetOptions{})
backupPVC, err := exposer.kubeClient.CoreV1().PersistentVolumeClaims(ownerObject.Namespace).Get(context.Background(), ownerObject.Name, metav1.GetOptions{})
assert.NoError(t, err)
expectedVS, err := exposer.csiSnapshotClient.VolumeSnapshots(ownerObject.Namespace).Get(context.Background(), ownerObject.Name, metav1.GetOptions{})
@@ -377,6 +429,12 @@ func TestExpose(t *testing.T) {
assert.Equal(t, expectedVSC.Spec.DeletionPolicy, vscObj.Spec.DeletionPolicy)
assert.Equal(t, expectedVSC.Spec.Driver, vscObj.Spec.Driver)
assert.Equal(t, *expectedVSC.Spec.VolumeSnapshotClassName, *vscObj.Spec.VolumeSnapshotClassName)
if test.expectedVolumeSize != nil {
assert.Equal(t, *test.expectedVolumeSize, backupPVC.Spec.Resources.Requests[corev1.ResourceStorage])
} else {
assert.Equal(t, *resource.NewQuantity(restoreSize, ""), backupPVC.Spec.Resources.Requests[corev1.ResourceStorage])
}
} else {
assert.EqualError(t, err, test.err)
}
+1
View File
@@ -105,6 +105,7 @@ func DaemonSet(namespace string, opts ...podTemplateOption) *appsv1.DaemonSet {
{
Name: "node-agent",
Image: c.image,
Ports: containerPorts(),
ImagePullPolicy: pullPolicy,
Command: []string{
"/velero",
+11
View File
@@ -48,6 +48,7 @@ type podTemplateConfig struct {
uploaderType string
defaultSnapshotMoveData bool
privilegedNodeAgent bool
disableInformerCache bool
}
func WithImage(image string) podTemplateOption {
@@ -144,6 +145,12 @@ func WithDefaultSnapshotMoveData() podTemplateOption {
}
}
func WithDisableInformerCache() podTemplateOption {
return func(c *podTemplateConfig) {
c.disableInformerCache = true
}
}
func WithServiceAccountName(sa string) podTemplateOption {
return func(c *podTemplateConfig) {
c.serviceAccountName = sa
@@ -185,6 +192,10 @@ func Deployment(namespace string, opts ...podTemplateOption) *appsv1.Deployment
args = append(args, "--default-snapshot-move-data=true")
}
if c.disableInformerCache {
args = append(args, "--disable-informer-cache=true")
}
if len(c.uploaderType) > 0 {
args = append(args, fmt.Sprintf("--uploader-type=%s", c.uploaderType))
}
+4
View File
@@ -64,4 +64,8 @@ func TestDeployment(t *testing.T) {
deploy = Deployment("velero", WithServiceAccountName("test-sa"))
assert.Equal(t, "test-sa", deploy.Spec.Template.Spec.ServiceAccountName)
deploy = Deployment("velero", WithDisableInformerCache())
assert.Len(t, deploy.Spec.Template.Spec.Containers[0].Args, 2)
assert.Equal(t, "--disable-informer-cache=true", deploy.Spec.Template.Spec.Containers[0].Args[1])
}
+5
View File
@@ -253,6 +253,7 @@ type VeleroOptions struct {
DefaultVolumesToFsBackup bool
UploaderType string
DefaultSnapshotMoveData bool
DisableInformerCache bool
}
func AllCRDs() *unstructured.UnstructuredList {
@@ -357,6 +358,10 @@ func AllResources(o *VeleroOptions) *unstructured.UnstructuredList {
deployOpts = append(deployOpts, WithDefaultSnapshotMoveData())
}
if o.DisableInformerCache {
deployOpts = append(deployOpts, WithDisableInformerCache())
}
deploy := Deployment(o.Namespace, deployOpts...)
if err := appendUnstructured(resources, deploy); err != nil {
+1 -1
View File
@@ -41,7 +41,7 @@ const (
// PluginKindRestoreItemAction represents a restore item action plugin.
PluginKindRestoreItemAction PluginKind = "RestoreItemAction"
// PluginKindRestoreItemAction represents a v2 restore item action plugin.
// PluginKindRestoreItemActionV2 represents a v2 restore item action plugin.
PluginKindRestoreItemActionV2 PluginKind = "RestoreItemActionV2"
// PluginKindDeleteItemAction represents a delete item action plugin.
+20 -37
View File
@@ -21,12 +21,10 @@ import (
"context"
"os"
goerr "errors"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/pkg/errors"
)
@@ -73,27 +71,28 @@ func GetS3ResticEnvVars(config map[string]string) (map[string]string, error) {
// GetS3Credentials gets the S3 credential values according to the information
// of the provided config or the system's environment variables
func GetS3Credentials(config map[string]string) (*credentials.Value, error) {
func GetS3Credentials(config map[string]string) (*aws.Credentials, error) {
if os.Getenv(awsRoleEnvVar) != "" {
return nil, nil
}
opts := session.Options{}
var opts []func(*awsconfig.LoadOptions) error
credentialsFile := config[CredentialsFileKey]
if credentialsFile == "" {
credentialsFile = os.Getenv(awsCredentialsFileEnvVar)
}
if credentialsFile != "" {
opts.SharedConfigFiles = append(opts.SharedConfigFiles, credentialsFile)
opts.SharedConfigState = session.SharedConfigEnable
opts = append(opts, awsconfig.WithSharedCredentialsFiles([]string{credentialsFile}),
// To support the existing use case where config file is passed
// as credentials of a BSL
awsconfig.WithSharedConfigFiles([]string{credentialsFile}))
}
sess, err := session.NewSessionWithOptions(opts)
cfg, err := awsconfig.LoadDefaultConfig(context.Background(), opts...)
if err != nil {
return nil, err
}
creds, err := sess.Config.Credentials.Get()
creds, err := cfg.Credentials.Retrieve(context.Background())
return &creds, err
}
@@ -101,33 +100,17 @@ func GetS3Credentials(config map[string]string) (*credentials.Value, error) {
// GetAWSBucketRegion returns the AWS region that a bucket is in, or an error
// if the region cannot be determined.
func GetAWSBucketRegion(bucket string) (string, error) {
sess, err := session.NewSession()
cfg, err := awsconfig.LoadDefaultConfig(context.Background())
if err != nil {
return "", errors.WithStack(err)
}
var region string
var requestErrs []error
for _, partition := range endpoints.DefaultPartitions() {
for regionHint := range partition.Regions() {
region, err = s3manager.GetBucketRegion(context.Background(), sess, bucket, regionHint)
if err != nil {
requestErrs = append(requestErrs, errors.Wrapf(err, "error to get region with hint %s", regionHint))
}
// we only need to try a single region hint per partition, so break after the first
break
}
if region != "" {
return region, nil
}
client := s3.NewFromConfig(cfg)
region, err := s3manager.GetBucketRegion(context.Background(), client, bucket)
if err != nil {
return "", errors.WithStack(err)
}
if requestErrs == nil {
return "", errors.Errorf("unable to determine region by bucket %s", bucket)
} else {
return "", errors.Wrapf(goerr.Join(requestErrs...), "error to get region by bucket %s", bucket)
if region == "" {
return "", errors.New("unable to determine bucket's region")
}
return region, nil
}
+6 -4
View File
@@ -433,7 +433,7 @@ func getStorageCredentials(backupLocation *velerov1api.BackupStorageLocation, cr
if credValue != nil {
result[udmrepo.StoreOptionS3KeyID] = credValue.AccessKeyID
result[udmrepo.StoreOptionS3Provider] = credValue.ProviderName
result[udmrepo.StoreOptionS3Provider] = credValue.Source
result[udmrepo.StoreOptionS3SecretKey] = credValue.SecretAccessKey
result[udmrepo.StoreOptionS3Token] = credValue.SessionToken
}
@@ -477,9 +477,11 @@ func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repo
var err error
if s3URL == "" {
region, err = getS3BucketRegion(bucket)
if err != nil {
return map[string]string{}, errors.Wrap(err, "error get s3 bucket region")
if region == "" {
region, err = getS3BucketRegion(bucket)
if err != nil {
return map[string]string{}, errors.Wrap(err, "error get s3 bucket region")
}
}
s3URL = fmt.Sprintf("s3-%s.amazonaws.com", region)
+8 -8
View File
@@ -22,7 +22,7 @@ import (
"errors"
"testing"
awscredentials "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/kopia/kopia/repo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -45,7 +45,7 @@ func TestGetStorageCredentials(t *testing.T) {
credFileStore *credmock.FileStore
credStoreError error
credStorePath string
getS3Credentials func(map[string]string) (*awscredentials.Value, error)
getS3Credentials func(map[string]string) (*aws.Credentials, error)
getGCPCredentials func(map[string]string) string
expected map[string]string
expectedErr string
@@ -89,8 +89,8 @@ func TestGetStorageCredentials(t *testing.T) {
},
},
},
getS3Credentials: func(config map[string]string) (*awscredentials.Value, error) {
return &awscredentials.Value{
getS3Credentials: func(config map[string]string) (*aws.Credentials, error) {
return &aws.Credentials{
AccessKeyID: "from: " + config["credentialsFile"],
}, nil
},
@@ -115,8 +115,8 @@ func TestGetStorageCredentials(t *testing.T) {
},
credFileStore: new(credmock.FileStore),
credStorePath: "credentials-from-credential-key",
getS3Credentials: func(config map[string]string) (*awscredentials.Value, error) {
return &awscredentials.Value{
getS3Credentials: func(config map[string]string) (*aws.Credentials, error) {
return &aws.Credentials{
AccessKeyID: "from: " + config["credentialsFile"],
}, nil
},
@@ -138,7 +138,7 @@ func TestGetStorageCredentials(t *testing.T) {
},
},
},
getS3Credentials: func(config map[string]string) (*awscredentials.Value, error) {
getS3Credentials: func(config map[string]string) (*aws.Credentials, error) {
return nil, errors.New("fake error")
},
credFileStore: new(credmock.FileStore),
@@ -153,7 +153,7 @@ func TestGetStorageCredentials(t *testing.T) {
Config: map[string]string{},
},
},
getS3Credentials: func(config map[string]string) (*awscredentials.Value, error) {
getS3Credentials: func(config map[string]string) (*aws.Credentials, error) {
return nil, nil
},
credFileStore: new(credmock.FileStore),
+9 -8
View File
@@ -51,14 +51,15 @@ func resourceKey(obj runtime.Object) string {
type Request struct {
*velerov1api.Restore
Log logrus.FieldLogger
Backup *velerov1api.Backup
PodVolumeBackups []*velerov1api.PodVolumeBackup
VolumeSnapshots []*volume.Snapshot
BackupReader io.Reader
RestoredItems map[itemKey]restoredItemStatus
itemOperationsList *[]*itemoperation.RestoreOperation
ResourceModifiers *resourcemodifiers.ResourceModifiers
Log logrus.FieldLogger
Backup *velerov1api.Backup
PodVolumeBackups []*velerov1api.PodVolumeBackup
VolumeSnapshots []*volume.Snapshot
BackupReader io.Reader
RestoredItems map[itemKey]restoredItemStatus
itemOperationsList *[]*itemoperation.RestoreOperation
ResourceModifiers *resourcemodifiers.ResourceModifiers
DisableInformerCache bool
}
type restoredItemStatus struct {
+152 -12
View File
@@ -22,6 +22,7 @@ import (
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"sort"
"strings"
@@ -42,6 +43,8 @@ import (
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/informers"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -65,6 +68,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/podvolume"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/collections"
csiutil "github.com/vmware-tanzu/velero/pkg/util/csi"
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
"github.com/vmware-tanzu/velero/pkg/util/kube"
"github.com/vmware-tanzu/velero/pkg/util/results"
@@ -299,6 +303,8 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
resourceTerminatingTimeout: kr.resourceTerminatingTimeout,
resourceTimeout: kr.resourceTimeout,
resourceClients: make(map[resourceClientKey]client.Dynamic),
dynamicInformerFactories: make(map[string]*informerFactoryWithContext),
resourceInformers: make(map[resourceClientKey]informers.GenericInformer),
restoredItems: req.RestoredItems,
renamedPVs: make(map[string]string),
pvRenamer: kr.pvRenamer,
@@ -312,6 +318,7 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
kbClient: kr.kbClient,
itemOperationsList: req.GetItemOperationsList(),
resourceModifiers: req.ResourceModifiers,
disableInformerCache: req.DisableInformerCache,
}
return restoreCtx.execute()
@@ -345,6 +352,8 @@ type restoreContext struct {
resourceTerminatingTimeout time.Duration
resourceTimeout time.Duration
resourceClients map[resourceClientKey]client.Dynamic
dynamicInformerFactories map[string]*informerFactoryWithContext
resourceInformers map[resourceClientKey]informers.GenericInformer
restoredItems map[itemKey]restoredItemStatus
renamedPVs map[string]string
pvRenamer func(string) (string, error)
@@ -359,6 +368,7 @@ type restoreContext struct {
kbClient crclient.Client
itemOperationsList *[]*itemoperation.RestoreOperation
resourceModifiers *resourcemodifiers.ResourceModifiers
disableInformerCache bool
}
type resourceClientKey struct {
@@ -366,6 +376,12 @@ type resourceClientKey struct {
namespace string
}
type informerFactoryWithContext struct {
factory dynamicinformer.DynamicSharedInformerFactory
context go_context.Context
cancel go_context.CancelFunc
}
// getOrderedResources returns an ordered list of resource identifiers to restore,
// based on the provided resource priorities and backup contents. The returned list
// begins with all of the high prioritized resources (in order), ends with all of
@@ -416,6 +432,17 @@ func (ctx *restoreContext) execute() (results.Result, results.Result) {
}
}()
// Need to stop all informers if enabled
if !ctx.disableInformerCache {
defer func() {
// Call the cancel func to close the channel for each started informer
for _, factory := range ctx.dynamicInformerFactories {
factory.cancel()
}
// After upgrading to client-go 0.27 or newer, also call Shutdown for each informer factory
}()
}
// Need to set this for additionalItems to be restored.
ctx.restoreDir = dir
@@ -520,6 +547,32 @@ func (ctx *restoreContext) execute() (results.Result, results.Result) {
warnings.Merge(&w)
errs.Merge(&e)
// initialize informer caches for selected resources if enabled
if !ctx.disableInformerCache {
// CRD informer will have already been initialized if any CRDs were created,
// but already-initialized informers aren't re-initialized because getGenericInformer
// looks for an existing one first.
factoriesToStart := make(map[string]*informerFactoryWithContext)
for _, informerResource := range selectedResourceCollection {
gr := schema.ParseGroupResource(informerResource.resource)
for _, items := range informerResource.selectedItemsByNamespace {
// don't use ns key since it represents original ns, not mapped ns
if len(items) == 0 {
continue
}
// use the first item in the list to initialize the informer. The rest of the list
// should share the same gvr and namespace
_, factory := ctx.getGenericInformerInternal(gr, items[0].version, items[0].targetNamespace)
if factory != nil {
factoriesToStart[items[0].targetNamespace] = factory
}
}
}
for _, factoryWithContext := range factoriesToStart {
factoryWithContext.factory.WaitForCacheSync(factoryWithContext.context.Done())
}
}
// reset processedItems and totalItems before processing full resource list
processedItems = 0
totalItems = 0
@@ -934,11 +987,14 @@ func (ctx *restoreContext) itemsAvailable(action framework.RestoreItemResolvedAc
return available, err
}
func (ctx *restoreContext) getResourceClient(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace string) (client.Dynamic, error) {
key := resourceClientKey{
resource: groupResource.WithVersion(obj.GroupVersionKind().Version),
func getResourceClientKey(groupResource schema.GroupResource, version, namespace string) resourceClientKey {
return resourceClientKey{
resource: groupResource.WithVersion(version),
namespace: namespace,
}
}
func (ctx *restoreContext) getResourceClient(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace string) (client.Dynamic, error) {
key := getResourceClientKey(groupResource, obj.GroupVersionKind().Version, namespace)
if client, ok := ctx.resourceClients[key]; ok {
return client, nil
@@ -962,6 +1018,49 @@ func (ctx *restoreContext) getResourceClient(groupResource schema.GroupResource,
return client, nil
}
// if new informer is created, non-nil factory is returned
func (ctx *restoreContext) getGenericInformerInternal(groupResource schema.GroupResource, version, namespace string) (informers.GenericInformer, *informerFactoryWithContext) {
var returnFactory *informerFactoryWithContext
key := getResourceClientKey(groupResource, version, namespace)
factoryWithContext, ok := ctx.dynamicInformerFactories[key.namespace]
if !ok {
factory := ctx.dynamicFactory.DynamicSharedInformerFactoryForNamespace(namespace)
informerContext, informerCancel := signal.NotifyContext(go_context.Background(), os.Interrupt)
factoryWithContext = &informerFactoryWithContext{
factory: factory,
context: informerContext,
cancel: informerCancel,
}
ctx.dynamicInformerFactories[key.namespace] = factoryWithContext
}
informer, ok := ctx.resourceInformers[key]
if !ok {
ctx.log.Infof("[debug] Creating factory for %s in namespace %s", key.resource, key.namespace)
informer = factoryWithContext.factory.ForResource(key.resource)
factoryWithContext.factory.Start(factoryWithContext.context.Done())
ctx.resourceInformers[key] = informer
returnFactory = factoryWithContext
}
return informer, returnFactory
}
func (ctx *restoreContext) getGenericInformer(groupResource schema.GroupResource, version, namespace string) informers.GenericInformer {
informer, factoryWithContext := ctx.getGenericInformerInternal(groupResource, version, namespace)
if factoryWithContext != nil {
factoryWithContext.factory.WaitForCacheSync(factoryWithContext.context.Done())
}
return informer
}
func (ctx *restoreContext) getResourceLister(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace string) cache.GenericNamespaceLister {
informer := ctx.getGenericInformer(groupResource, obj.GroupVersionKind().Version, namespace)
if namespace == "" {
return informer.Lister()
} else {
return informer.Lister().ByNamespace(namespace)
}
}
func getResourceID(groupResource schema.GroupResource, namespace, name string) string {
if namespace == "" {
return fmt.Sprintf("%s/%s", groupResource.String(), name)
@@ -970,6 +1069,20 @@ func getResourceID(groupResource schema.GroupResource, namespace, name string) s
return fmt.Sprintf("%s/%s/%s", groupResource.String(), namespace, name)
}
func (ctx *restoreContext) getResource(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace, name string) (*unstructured.Unstructured, error) {
lister := ctx.getResourceLister(groupResource, obj, namespace)
clusterObj, err := lister.Get(name)
if err != nil {
return nil, errors.Wrapf(err, "error getting resource from lister for %s, %s/%s", groupResource, namespace, name)
}
u, ok := clusterObj.(*unstructured.Unstructured)
if !ok {
ctx.log.WithError(errors.WithStack(fmt.Errorf("expected *unstructured.Unstructured but got %T", u))).Error("unable to understand entry returned from client")
return nil, fmt.Errorf("expected *unstructured.Unstructured but got %T", u)
}
return u, nil
}
func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupResource schema.GroupResource, namespace string) (results.Result, results.Result, bool) {
warnings, errs := results.Result{}, results.Result{}
// itemExists bool is used to determine whether to include this item in the "wait for additional items" list
@@ -1163,6 +1276,7 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
ctx.renamedPVs[oldName] = pvName
obj.SetName(pvName)
name = pvName
// Add the original PV name as an annotation.
annotations := obj.GetAnnotations()
@@ -1224,6 +1338,13 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
continue
}
// If the EnableCSI feature is not enabled, but the executing action is from CSI plugin, skip the action.
if csiutil.ShouldSkipAction(action.Name()) {
ctx.log.Infof("Skip action %s for resource %s:%s/%s, because the CSI feature is not enabled. Feature setting is %s.",
action.Name(), groupResource.String(), obj.GetNamespace(), obj.GetName(), features.Serialize())
continue
}
ctx.log.Infof("Executing item action for %v", &groupResource)
executeOutput, err := action.RestoreItemAction.Execute(&velero.RestoreItemActionExecuteInput{
Item: obj,
@@ -1382,27 +1503,44 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
}
ctx.log.Infof("Attempting to restore %s: %v", obj.GroupVersionKind().Kind, name)
createdObj, restoreErr := resourceClient.Create(obj)
if restoreErr == nil {
itemExists = true
ctx.restoredItems[itemKey] = restoredItemStatus{action: itemRestoreResultCreated, itemExists: itemExists}
// check if we want to treat the error as a warning, in some cases the creation call might not get executed due to object API validations
// and Velero might not get the already exists error type but in reality the object already exists
var fromCluster, createdObj *unstructured.Unstructured
var restoreErr error
// only attempt Get before Create if using informer cache, otherwise this will slow down restore into
// new namespace
if !ctx.disableInformerCache {
ctx.log.Debugf("Checking for existence %s: %v", obj.GroupVersionKind().Kind, name)
fromCluster, err = ctx.getResource(groupResource, obj, namespace, name)
}
if err != nil || fromCluster == nil {
// couldn't find the resource, attempt to create
ctx.log.Debugf("Creating %s: %v", obj.GroupVersionKind().Kind, name)
createdObj, restoreErr = resourceClient.Create(obj)
if restoreErr == nil {
itemExists = true
ctx.restoredItems[itemKey] = restoredItemStatus{action: itemRestoreResultCreated, itemExists: itemExists}
}
}
isAlreadyExistsError, err := isAlreadyExistsError(ctx, obj, restoreErr, resourceClient)
if err != nil {
errs.Add(namespace, err)
return warnings, errs, itemExists
}
// check if we want to treat the error as a warning, in some cases the creation call might not get executed due to object API validations
// and Velero might not get the already exists error type but in reality the object already exists
var fromCluster *unstructured.Unstructured
if restoreErr != nil {
// check for the existence of the object in cluster, if no error then it implies that object exists
// and if err then we want to judge whether there is an existing error in the previous creation.
// if so, we will return the 'get' error.
// otherwise, we will return the original creation error.
fromCluster, err = resourceClient.Get(name, metav1.GetOptions{})
if !ctx.disableInformerCache {
fromCluster, err = ctx.getResource(groupResource, obj, namespace, name)
} else {
fromCluster, err = resourceClient.Get(name, metav1.GetOptions{})
}
if err != nil && isAlreadyExistsError {
ctx.log.Errorf("Error retrieving in-cluster version of %s: %v", kube.NamespaceAndName(obj), err)
errs.Add(namespace, err)
@@ -1947,6 +2085,7 @@ type restoreableItem struct {
path string
targetNamespace string
name string
version string // used for initializing informer cache
}
// getOrderedResourceCollection iterates over list of ordered resource
@@ -2136,6 +2275,7 @@ func (ctx *restoreContext) getSelectedRestoreableItems(resource, targetNamespace
path: itemPath,
name: item,
targetNamespace: targetNamespace,
version: obj.GroupVersionKind().Version,
}
restorable.selectedItemsByNamespace[originalNamespace] =
append(restorable.selectedItemsByNamespace[originalNamespace], selectedItem)
+29 -7
View File
@@ -861,6 +861,7 @@ func TestRestoreItems(t *testing.T) {
tarball io.Reader
want []*test.APIResource
expectedRestoreItems map[itemKey]restoredItemStatus
disableInformer bool
}{
{
name: "metadata uid/resourceVersion/etc. gets removed",
@@ -1017,6 +1018,26 @@ func TestRestoreItems(t *testing.T) {
apiResources: []*test.APIResource{
test.Secrets(builder.ForSecret("ns-1", "sa-1").Data(map[string][]byte{"foo": []byte("bar")}).Result()),
},
disableInformer: true,
want: []*test.APIResource{
test.Secrets(builder.ForSecret("ns-1", "sa-1").ObjectMeta(builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1")).Data(map[string][]byte{"key-1": []byte("value-1")}).Result()),
},
expectedRestoreItems: map[itemKey]restoredItemStatus{
{resource: "v1/Namespace", namespace: "", name: "ns-1"}: {action: "created", itemExists: true},
{resource: "v1/Secret", namespace: "ns-1", name: "sa-1"}: {action: "updated", itemExists: true},
},
},
{
name: "update secret data and labels when secret exists in cluster and is not identical to the backed up one, existing resource policy is update, using informer cache",
restore: defaultRestore().ExistingResourcePolicy("update").Result(),
backup: defaultBackup().Result(),
tarball: test.NewTarWriter(t).
AddItems("secrets", builder.ForSecret("ns-1", "sa-1").Data(map[string][]byte{"key-1": []byte("value-1")}).Result()).
Done(),
apiResources: []*test.APIResource{
test.Secrets(builder.ForSecret("ns-1", "sa-1").Data(map[string][]byte{"foo": []byte("bar")}).Result()),
},
disableInformer: false,
want: []*test.APIResource{
test.Secrets(builder.ForSecret("ns-1", "sa-1").ObjectMeta(builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1")).Data(map[string][]byte{"key-1": []byte("value-1")}).Result()),
},
@@ -1175,13 +1196,14 @@ func TestRestoreItems(t *testing.T) {
}
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
PodVolumeBackups: nil,
VolumeSnapshots: nil,
BackupReader: tc.tarball,
RestoredItems: map[itemKey]restoredItemStatus{},
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
PodVolumeBackups: nil,
VolumeSnapshots: nil,
BackupReader: tc.tarball,
RestoredItems: map[itemKey]restoredItemStatus{},
DisableInformerCache: tc.disableInformer,
}
warnings, errs := h.restorer.Restore(
data,
+1
View File
@@ -59,6 +59,7 @@ func NewAPIServer(t *testing.T) *APIServer {
{Group: "velero.io", Version: "v1", Resource: "backups"}: "BackupList",
{Group: "extensions", Version: "v1", Resource: "deployments"}: "ExtDeploymentsList",
{Group: "velero.io", Version: "v1", Resource: "deployments"}: "VeleroDeploymentsList",
{Group: "velero.io", Version: "v2alpha1", Resource: "datauploads"}: "DataUploadsList",
})
discoveryClient = &DiscoveryClient{FakeDiscovery: kubeClient.Discovery().(*discoveryfake.FakeDiscovery)}
)
+6
View File
@@ -22,6 +22,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic/dynamicinformer"
"github.com/vmware-tanzu/velero/pkg/client"
)
@@ -37,6 +38,11 @@ func (df *FakeDynamicFactory) ClientForGroupVersionResource(gv schema.GroupVersi
return args.Get(0).(client.Dynamic), args.Error(1)
}
func (df *FakeDynamicFactory) DynamicSharedInformerFactoryForNamespace(namespace string) dynamicinformer.DynamicSharedInformerFactory {
args := df.Called(namespace)
return args.Get(0).(dynamicinformer.DynamicSharedInformerFactory)
}
type FakeDynamicClient struct {
mock.Mock
}
+3
View File
@@ -1,3 +1,6 @@
//go:build !windows
// +build !windows
/*
Copyright The Velero Contributors.
@@ -0,0 +1,30 @@
//go:build windows
// +build windows
/*
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kopia
import (
"fmt"
"github.com/kopia/kopia/fs"
)
func getLocalBlockEntry(sourcePath string) (fs.Entry, error) {
return nil, fmt.Errorf("block mode is not supported for Windows")
}
+3
View File
@@ -1,3 +1,6 @@
//go:build !windows
// +build !windows
/*
Copyright The Velero Contributors.
@@ -0,0 +1,42 @@
//go:build windows
// +build windows
/*
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kopia
import (
"context"
"fmt"
"github.com/kopia/kopia/fs"
"github.com/kopia/kopia/snapshot/restore"
)
type BlockOutput struct {
*restore.FilesystemOutput
targetFileName string
}
func (o *BlockOutput) WriteFile(ctx context.Context, relativePath string, remoteFile fs.File) error {
return fmt.Errorf("block mode is not supported for Windows")
}
func (o *BlockOutput) BeginDirectory(ctx context.Context, relativePath string, e fs.Directory) error {
return fmt.Errorf("block mode is not supported for Windows")
}
+2 -1
View File
@@ -27,6 +27,7 @@ import (
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/repo/content/index"
"github.com/kopia/kopia/repo/manifest"
"github.com/kopia/kopia/repo/object"
)
@@ -140,7 +141,7 @@ func (sr *shimRepository) Refresh(ctx context.Context) error {
// ContentInfo not supported
func (sr *shimRepository) ContentInfo(ctx context.Context, contentID content.ID) (content.Info, error) {
return nil, errors.New("ContentInfo is not supported")
return index.Info{}, errors.New("ContentInfo is not supported")
}
// PrefetchContents is not supported by unified repo
+10 -1
View File
@@ -26,7 +26,7 @@ import (
"github.com/pkg/errors"
)
// NewCredential chains the config credential and workload identity credential
// NewCredential chains the config credential , workload identity credential , managed identity credential
func NewCredential(creds map[string]string, options policy.ClientOptions) (azcore.TokenCredential, error) {
var (
credential []azcore.TokenCredential
@@ -60,6 +60,15 @@ func NewCredential(creds map[string]string, options policy.ClientOptions) (azcor
errMsgs = append(errMsgs, err.Error())
}
//managed identity credential
o := &azidentity.ManagedIdentityCredentialOptions{ClientOptions: options, ID: azidentity.ClientID(creds[CredentialKeyClientID])}
msi, err := azidentity.NewManagedIdentityCredential(o)
if err == nil {
credential = append(credential, msi)
} else {
errMsgs = append(errMsgs, err.Error())
}
if len(credential) == 0 {
return nil, errors.Errorf("failed to create Azure credential: %s", strings.Join(errMsgs, "\n\t"))
}
+10 -2
View File
@@ -17,7 +17,9 @@ limitations under the License.
package azure
import (
"context"
"testing"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
@@ -26,10 +28,15 @@ import (
func TestNewCredential(t *testing.T) {
options := policy.ClientOptions{}
// no credentials
creds := map[string]string{}
_, err := NewCredential(creds, options)
tokenCredential, _ := NewCredential(creds, options)
var scopes []string
scopes = append(scopes, "https://management.core.windows.net//.default")
ctx, _ := context.WithTimeout(context.Background(), time.Second*2)
_, err := tokenCredential.GetToken(ctx, policy.TokenRequestOptions{Scopes: scopes})
require.NotNil(t, err)
// config credential
@@ -40,6 +47,7 @@ func TestNewCredential(t *testing.T) {
}
_, err = NewCredential(creds, options)
require.Nil(t, err)
}
func Test_newConfigCredential(t *testing.T) {
+32
View File
@@ -0,0 +1,32 @@
/*
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csi
import (
"strings"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/features"
)
const (
csiPluginNamePrefix = "velero.io/csi-"
)
func ShouldSkipAction(actionName string) bool {
return !features.IsEnabled(velerov1api.CSIFeatureFlag) && strings.Contains(actionName, csiPluginNamePrefix)
}
+36
View File
@@ -0,0 +1,36 @@
/*
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csi
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/vmware-tanzu/velero/pkg/features"
)
func TestCSIFeatureNotEnabledAndPluginIsFromCSI(t *testing.T) {
features.NewFeatureFlagSet("EnableCSI")
require.False(t, ShouldSkipAction("abc"))
require.False(t, ShouldSkipAction("velero.io/csi-pvc-backupper"))
features.NewFeatureFlagSet("")
require.True(t, ShouldSkipAction("velero.io/csi-pvc-backupper"))
require.False(t, ShouldSkipAction("abc"))
}
@@ -76,6 +76,32 @@ Please do notice there is also side effect that may not be expected. Because sch
If there is possibility the schedule will be disable to not create backup anymore, and the created backups are still useful. Please do not enable this option. For detail, please reference to [Backups created by a schedule with useOwnerReferenceInBackup set do not get synced properly](https://github.com/vmware-tanzu/velero/issues/4093).
Some GitOps tools have configurations to avoid pruning the day 2 backups generated from the schedule.
For example, the ArgoCD has two ways to do that:
* Add annotations to schedule. This method makes ArgoCD ignore the schedule from syncing, so the generated backups are ignored too, but it has a side effect. When deleting the schedule from the GitOps manifest, the schedule can not be deleted. User needs to do it manually.
``` yaml
annotations:
argocd.argoproj.io/compare-options: IgnoreExtraneous
argocd.argoproj.io/sync-options: Delete=false,Prune=false
```
* If ArgoCD is deployed by ArgoCD-Operator, there is another option: [resourceExclusions](https://argocd-operator.readthedocs.io/en/latest/reference/argocd/#resource-exclusions-example). This is an example, which means ArgoCD operator should ignore `Backup` and `Restore` in `velero.io` group in the `velero` namespace for all managed k8s cluster.
``` yaml
apiVersion: argoproj.io/v1alpha1
kind: ArgoCD
metadata:
name: velero-argocd
namespace: velero
spec:
resourceExclusions: |
- apiGroups:
- velero.io
kinds:
- Backup
- Restore
clusters:
- "*"
```
#### Cannot support backup data immutability
Starting from 1.11, Velero's backups may not work as expected when the target object storage has some kind of an "immutability" option configured. These options are known by different names (see links below for some examples). The main reason is that Velero first saves the state of a backup as Finalizing and then checks whether there are any async operations in progress. If there are, it needs to wait for all of them to be finished before moving the backup state to Complete. If there are no async operations, the state is moved to Complete right away. In either case, Velero needs to modify the metadata in object storage and that will not be possible if some kind of immutability is configured on the object storage.
@@ -248,6 +248,7 @@ that anyone who has access to your backup storage can decrypt your backup data**
to the backup storage appropriately.
- [Velero built-in data mover] Even though the backup data could be incrementally preserved, for a single file data, Velero built-in data mover leverages on deduplication to find the difference to be saved. This means that large files (such as ones storing a database) will take a long time to scan for data deduplication, even if the actual difference is small.
- [Velero built-in data mover] You may need to [customize the resource limits][11] to make sure backups complete successfully for massive small files or large backup size cases, for more details refer to [Velero file system level backup performance guide][12].
- The block mode is supported by the Kopia uploader, but it only supports non-Windows platforms, because the block mode code invokes some system calls that are not present in the Windows platform.
## Troubleshooting
@@ -354,6 +354,7 @@ For this reason, FSB can only backup volumes that are mounted by a pod and not d
(without running pods), some Velero users overcame this limitation running a staging pod (i.e. a busybox or alpine container
with an infinite sleep) to mount these PVC/PV pairs prior taking a Velero backup.
- Velero File System Backup expects volumes to be mounted under `<hostPath>/<pod UID>` (`hostPath` is configurable as mentioned in [Configure Node Agent DaemonSet spec](#configure-node-agent-daemonset-spec)). Some Kubernetes systems (i.e., [vCluster][11]) don't mount volumes under the `<pod UID>` sub-dir, Velero File System Backup is not working with them.
- File system restores of the same pod won't start until all the volumes of the pod get bound, even though some of the volumes have been bound and ready for restore. An a result, if a pod has multiple volumes, while only part of the volumes are restored by file system restore, these file system restores won't start until the other volumes are restored completely by other restore types (i.e., [CSI Snapshot Restore][12], [CSI Snapshot Data Movement][13]), the file system restores won't happen concurrently with those other types of restores.
## Customize Restore Helper Container
@@ -596,3 +597,5 @@ To solve this, a controller was written by Thomann Bits&Beats: [velero-pvc-watch
[9]: https://github.com/restic/restic/issues/1800
[10]: customize-installation.md#default-pod-volume-backup-to-file-system-backup
[11]: https://www.vcluster.com/
[12]: csi.md
[13]: csi-snapshot-data-movement.md
@@ -131,7 +131,7 @@ These are the steps to update the Velero Homebrew version.
- If you don't already have one, create a [GitHub access token for Homebrew](https://github.com/settings/tokens/new?scopes=gist,public_repo&description=Homebrew)
- Run `export HOMEBREW_GITHUB_API_TOKEN=your_token_here` on your command line to make sure that `brew` can work on GitHub on your behalf.
- Run `hack/release-tools/brew-update.sh`. This script will download the necessary files, do the checks, and invoke the brew helper to submit the PR, which will open in your browser.
- Update Windows Chocolatey version. From a Windows computer, follow the step-by-step instructions to [create the Windows Chocolatey package for Velero CLI](https://github.com/adamrushuk/velero-choco/blob/main/README.md)
- Update Windows Chocolatey version. From a Windows computer, follow the step-by-step instructions to [create the Windows Chocolatey package for Velero CLI](https://github.com/adamrushuk/velero-choco/blob/main/README.md). Please update the `tools\chocolateyinstall.ps1` file content according to [the existing Velero chocolatey package install script file](https://community.chocolatey.org/packages/velero#files). The current Velero chocolatey package maintainer is [Adam Rush](https://github.com/adamrushuk). It's possible others don't have permission to upload the new version. If so, please contact [Adam Rush](https://github.com/adamrushuk) for help.
## Plugins
+23 -2
View File
@@ -262,8 +262,8 @@ Velero only support volume resource policies currently, other kinds of resource
driver: aws.ebs.csi.driver
# pv matches one of the storage class list
storageClass:
- gp2
- standard
- gp2
- standard
action:
type: skip
- conditions:
@@ -289,6 +289,14 @@ Velero only support volume resource policies currently, other kinds of resource
csi: {}
action:
type: skip
- conditions:
volumeTypes:
- emptyDir
- downwardAPI
- configmap
- cinder
action:
type: skip
```
**Supported conditions**
@@ -336,6 +344,19 @@ Velero supported conditions and format listed below:
```
For volume provisioned by [Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes) support all above attributes, but for pod [Volume](https://kubernetes.io/docs/concepts/storage/volumes) only support filtered by volume source.
- volume types
Support filter volumes by types
```yaml
volumeTypes:
# matches volumes listed below
- emptyDir
- downwardAPI
- configmap
- cinder
```
Volume types could be found in [Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes) and pod [Volume](https://kubernetes.io/docs/concepts/storage/volumes)
**Resource policies rules**
- Velero already has lots of include or exclude filters. the resource policies are the final filters after others include or exclude filters in one backup processing workflow. So if use a defined similar filter like the opt-in approach to backup one pod volume but skip backup of the same pod volume in resource policies, as resource policies are the final filters that are applied, the volume will not be backed up.
- If volume resource policies conflict with themselves the first matched policy will be respected when many policies are defined.
+2
View File
@@ -181,6 +181,8 @@ Below are the annotations that can be added to a pod to specify exec restore hoo
* How long to wait once execution begins. Defaults is 30 seconds. Optional.
* `post.hook.restore.velero.io/wait-timeout`
* How long to wait for a container to become ready. This should be long enough for the container to start plus any preceding hooks in the same container to complete. The wait timeout begins when the container is restored and may require time for the image to pull and volumes to mount. If not set the restore will wait indefinitely. Optional.
* `post.hook.restore.velero.io/wait-for-ready`
* String representation of a boolean that ensure command will be launched when underlying container is fully [Ready](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes). Use with caution because if only one restore hook for a pod consists of `WaitForReady` flag as "true", all the other hook executions for that pod, whatever their origin (`Backup` or `Restore` CRD), will wait for `Ready` state too. Any value except "true" will be considered as "false". Defaults is false. Optional.
#### Exec Restore Hooks As Pod Annotation Example
@@ -261,6 +261,7 @@ that anyone who has access to your backup storage can decrypt your backup data**
to the backup storage appropriately.
- [Velero built-in data mover] Even though the backup data could be incrementally preserved, for a single file data, Velero built-in data mover leverages on deduplication to find the difference to be saved. This means that large files (such as ones storing a database) will take a long time to scan for data deduplication, even if the actual difference is small.
- [Velero built-in data mover] You may need to [customize the resource limits][11] to make sure backups complete successfully for massive small files or large backup size cases, for more details refer to [Velero file system level backup performance guide][12].
- The block mode is supported by the Kopia uploader, but it only supports non-Windows platforms, because the block mode code invokes some system calls that are not present in the Windows platform.
## Troubleshooting
+160 -199
View File
@@ -17,19 +17,24 @@ limitations under the License.
package providers
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/aws/aws-sdk-go-v2/config"
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/aws-sdk-go-v2/aws"
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/pkg/errors"
"golang.org/x/net/context"
@@ -55,10 +60,26 @@ const (
enableSharedConfigKey = "enableSharedConfig"
)
func newSessionOptions(config aws.Config, profile, caCert, credentialsFile, enableSharedConfig string) (session.Options, error) {
sessionOptions := session.Options{Config: config, Profile: profile}
if caCert != "" {
sessionOptions.CustomCABundle = strings.NewReader(caCert)
func newAWSConfig(region, profile, credentialsFile string, insecureSkipTLSVerify bool, caCert string) (aws.Config, error) {
empty := aws.Config{}
client := awshttp.NewBuildableClient().WithTransportOptions(func(tr *http.Transport) {
if len(caCert) > 0 {
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM([]byte(caCert))
if tr.TLSClientConfig == nil {
tr.TLSClientConfig = &tls.Config{
RootCAs: caCertPool,
}
} else {
tr.TLSClientConfig.RootCAs = caCertPool
}
}
tr.TLSClientConfig.InsecureSkipVerify = insecureSkipTLSVerify
})
opts := []func(*config.LoadOptions) error{
config.WithRegion(region),
config.WithSharedConfigProfile(profile),
config.WithHTTPClient(client),
}
if credentialsFile == "" && os.Getenv("AWS_SHARED_CREDENTIALS_FILE") != "" {
@@ -68,104 +89,60 @@ func newSessionOptions(config aws.Config, profile, caCert, credentialsFile, enab
if credentialsFile != "" {
if _, err := os.Stat(credentialsFile); err != nil {
if os.IsNotExist(err) {
return session.Options{}, errors.Wrapf(err, "provided credentialsFile does not exist")
return empty, errors.Wrapf(err, "provided credentialsFile does not exist")
}
return session.Options{}, errors.Wrapf(err, "could not get credentialsFile info")
return empty, errors.Wrapf(err, "could not get credentialsFile info")
}
sessionOptions.SharedConfigFiles = append(sessionOptions.SharedConfigFiles, credentialsFile)
sessionOptions.SharedConfigState = session.SharedConfigEnable
opts = append(opts, config.WithSharedCredentialsFiles([]string{credentialsFile}),
config.WithSharedConfigFiles([]string{credentialsFile}))
}
return sessionOptions, nil
awsConfig, err := config.LoadDefaultConfig(context.Background(), opts...)
if err != nil {
return empty, errors.Wrapf(err, "could not load config")
}
if _, err := awsConfig.Credentials.Retrieve(context.Background()); err != nil {
return empty, errors.WithStack(err)
}
return awsConfig, nil
}
// takes AWS session options to create a new session
func getSession(options session.Options) (*session.Session, error) {
sess, err := session.NewSessionWithOptions(options)
if err != nil {
return nil, errors.WithStack(err)
func newS3Client(cfg aws.Config, url string, forcePathStyle bool) (*s3.Client, error) {
opts := []func(*s3.Options){
func(o *s3.Options) {
o.UsePathStyle = forcePathStyle
},
}
if url != "" {
if !IsValidS3URLScheme(url) {
return nil, errors.Errorf("Invalid s3 url %s, URL must be valid according to https://golang.org/pkg/net/url/#Parse and start with http:// or https://", url)
}
opts = append(opts, func(o *s3.Options) {
o.BaseEndpoint = aws.String(url)
})
}
if _, err := sess.Config.Credentials.Get(); err != nil {
return nil, errors.WithStack(err)
}
return sess, nil
return s3.NewFromConfig(cfg, opts...), nil
}
// GetBucketRegion returns the AWS region that a bucket is in, or an error
// if the region cannot be determined.
func GetBucketRegion(bucket string) (string, error) {
var region string
session, err := session.NewSession()
cfg, err := config.LoadDefaultConfig(context.Background())
if err != nil {
return "", errors.WithStack(err)
}
for _, partition := range endpoints.DefaultPartitions() {
for regionHint := range partition.Regions() {
region, _ = s3manager.GetBucketRegion(context.Background(), session, bucket, regionHint)
// we only need to try a single region hint per partition, so break after the first
break
}
if region != "" {
return region, nil
}
}
return "", errors.New("unable to determine bucket's region")
}
func (s AWSStorage) CreateSession(credentialProfile, credentialsFile, enableSharedConfig, caCert, bucket, bslPrefix, bslConfig string) (*session.Session, error) {
var err error
config := flag.NewMap()
config.Set(bslConfig)
region := config.Data()["region"]
objectsInput := s3.ListObjectsV2Input{}
objectsInput.Bucket = aws.String(bucket)
objectsInput.Delimiter = aws.String("/")
s3URL := ""
s3ForcePathStyleVal := ""
s3ForcePathStyle := false
if s3ForcePathStyleVal != "" {
if s3ForcePathStyle, err = strconv.ParseBool(s3ForcePathStyleVal); err != nil {
return nil, errors.Wrapf(err, "could not parse %s (expected bool)", s3ForcePathStyleKey)
}
}
// AWS (not an alternate S3-compatible API) and region not
// explicitly specified: determine the bucket's region
if s3URL == "" && region == "" {
var err error
region, err = GetBucketRegion(bucket)
if err != nil {
return nil, err
}
}
serverConfig, err := newAWSConfig(s3URL, region, s3ForcePathStyle)
client := s3.NewFromConfig(cfg)
region, err := s3manager.GetBucketRegion(context.Background(), client, bucket)
if err != nil {
return nil, err
return "", errors.WithStack(err)
}
sessionOptions, err := newSessionOptions(*serverConfig, credentialProfile, caCert, credentialsFile, enableSharedConfig)
if err != nil {
return nil, err
if region == "" {
return "", errors.New("unable to determine bucket's region")
}
serverSession, err := getSession(sessionOptions)
if err != nil {
fmt.Println(err)
return nil, err
}
return serverSession, nil
return region, nil
}
// IsValidS3URLScheme returns true if the scheme is http:// or https://
@@ -180,34 +157,9 @@ func IsValidS3URLScheme(s3URL string) bool {
}
return true
}
func newAWSConfig(url, region string, forcePathStyle bool) (*aws.Config, error) {
awsConfig := aws.NewConfig().
WithRegion(region).
WithS3ForcePathStyle(forcePathStyle)
if url != "" {
if !IsValidS3URLScheme(url) {
return nil, errors.Errorf("Invalid s3 url %s, URL must be valid according to https://golang.org/pkg/net/url/#Parse and start with http:// or https://", url)
}
awsConfig = awsConfig.WithEndpointResolver(
endpoints.ResolverFunc(func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
if service == s3.EndpointsID {
return endpoints.ResolvedEndpoint{
URL: url,
}, nil
}
return endpoints.DefaultResolver().EndpointFor(service, region, optFns...)
}),
)
}
return awsConfig, nil
}
func (s AWSStorage) ListItems(client *s3.S3, objectsV2Input *s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, error) {
res, err := client.ListObjectsV2(objectsV2Input)
func (s AWSStorage) ListItems(client *s3.Client, objectsV2Input *s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, error) {
res, err := client.ListObjectsV2(context.Background(), objectsV2Input)
if err != nil {
return nil, err
}
@@ -215,14 +167,15 @@ func (s AWSStorage) ListItems(client *s3.S3, objectsV2Input *s3.ListObjectsV2Inp
return res, nil
}
func (s AWSStorage) DeleteItem(client *s3.S3, deleteObjectV2Input *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) {
res, err := client.DeleteObject(deleteObjectV2Input)
func (s AWSStorage) DeleteItem(client *s3.Client, deleteObjectV2Input *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) {
res, err := client.DeleteObject(context.Background(), deleteObjectV2Input)
if err != nil {
return nil, err
}
fmt.Println(res)
return res, nil
}
func (s AWSStorage) IsObjectsInBucket(cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupObject string) (bool, error) {
config := flag.NewMap()
config.Set(bslConfig)
@@ -235,38 +188,42 @@ func (s AWSStorage) IsObjectsInBucket(cloudCredentialsFile, bslBucket, bslPrefix
}
var err error
var s3Config *aws.Config
var sess *session.Session
var s3Config aws.Config
var s3Client *s3.Client
region := config.Data()["region"]
s3url := ""
if region == "" {
region, err = GetBucketRegion(bslBucket)
if err != nil {
return false, errors.Wrapf(err, "failed to get region for bucket %s", bslBucket)
}
}
if region == "minio" {
s3url = config.Data()["s3Url"]
s3Config = &aws.Config{
Credentials: credentials.NewSharedCredentials(cloudCredentialsFile, ""),
Endpoint: aws.String(s3url),
Region: aws.String(region),
DisableSSL: aws.Bool(true),
S3ForcePathStyle: aws.Bool(true),
s3Config, err = newAWSConfig(region, "", cloudCredentialsFile, true, "")
if err != nil {
return false, errors.Wrapf(err, "Failed to create AWS config of region %s", region)
}
sess, err = session.NewSession(s3Config)
s3Client, err = newS3Client(s3Config, s3url, true)
} else {
sess, err = s.CreateSession("", cloudCredentialsFile, "false", "", "", "", bslConfig)
s3Config, err = newAWSConfig(region, "", cloudCredentialsFile, false, "")
if err != nil {
return false, errors.Wrapf(err, "Failed to create AWS config of region %s", region)
}
s3Client, err = newS3Client(s3Config, s3url, true)
}
if err != nil {
return false, errors.Wrapf(err, fmt.Sprintf("Failed to create AWS session of region %s", region))
return false, errors.Wrapf(err, "failed to create S3 client of region %s", region)
}
svc := s3.New(sess)
bucketObjects, err := s.ListItems(svc, &objectsInput)
bucketObjects, err := s.ListItems(s3Client, &objectsInput)
if err != nil {
fmt.Println("Couldn't retrieve bucket items!")
return false, errors.Wrapf(err, "Couldn't retrieve bucket items")
}
for _, item := range bucketObjects.Contents {
fmt.Println(*item)
fmt.Println(item)
}
var backupNameInStorage string
for _, item := range bucketObjects.CommonPrefixes {
@@ -288,50 +245,64 @@ func (s AWSStorage) DeleteObjectsInBucket(cloudCredentialsFile, bslBucket, bslPr
config.Set(bslConfig)
var err error
var sess *session.Session
var s3Config aws.Config
var s3Client *s3.Client
region := config.Data()["region"]
s3url := ""
s3Config := &aws.Config{
Region: aws.String(region),
Credentials: credentials.NewSharedCredentials(cloudCredentialsFile, ""),
if region == "" {
region, err = GetBucketRegion(bslBucket)
if err != nil {
return errors.Wrapf(err, "failed to get region for bucket %s", bslBucket)
}
}
if region == "minio" {
s3url = config.Data()["s3Url"]
s3Config = &aws.Config{
Credentials: credentials.NewSharedCredentials(cloudCredentialsFile, ""),
Endpoint: aws.String(s3url),
Region: aws.String(region),
DisableSSL: aws.Bool(true),
S3ForcePathStyle: aws.Bool(true),
s3Config, err = newAWSConfig(region, "", cloudCredentialsFile, true, "")
if err != nil {
return errors.Wrapf(err, "Failed to create AWS config of region %s", region)
}
sess, err = session.NewSession(s3Config)
s3Client, err = newS3Client(s3Config, s3url, true)
} else {
sess, err = s.CreateSession("", cloudCredentialsFile, "false", "", "", "", bslConfig)
s3Config, err = newAWSConfig(region, "", cloudCredentialsFile, false, "")
if err != nil {
return errors.Wrapf(err, "Failed to create AWS config of region %s", region)
}
s3Client, err = newS3Client(s3Config, s3url, false)
}
if err != nil {
return errors.Wrapf(err, fmt.Sprintf("Failed to create AWS session of region %s", region))
return errors.Wrapf(err, "Failed to create S3 client of region %s", region)
}
svc := s3.New(sess)
fullPrefix := strings.Trim(bslPrefix, "/") + "/" + strings.Trim(backupObject, "/") + "/"
iter := s3manager.NewDeleteListIterator(svc, &s3.ListObjectsInput{
listInput := &s3.ListObjectsV2Input{
Bucket: aws.String(bslBucket),
Prefix: aws.String(fullPrefix),
}
// list all keys
var objectIds []s3types.ObjectIdentifier
p := s3.NewListObjectsV2Paginator(s3Client, listInput)
for p.HasMorePages() {
page, err := p.NextPage(context.Background())
if err != nil {
return errors.Wrapf(err, "failed to list objects in bucket %s", bslBucket)
}
for _, obj := range page.Contents {
objectIds = append(objectIds, s3types.ObjectIdentifier{Key: aws.String(*obj.Key)})
}
}
_, err = s3Client.DeleteObjects(context.Background(), &s3.DeleteObjectsInput{
Bucket: aws.String(bslBucket),
Delete: &s3types.Delete{Objects: objectIds},
})
if err := s3manager.NewBatchDeleteWithClient(svc).Delete(aws.BackgroundContext(), iter); err != nil {
return errors.Wrapf(err, "Fail to delete object")
if err != nil {
return errors.Wrapf(err, "failed to delete objects from bucket %s", bslBucket)
}
fmt.Printf("Deleted object(s) from bucket: %s %s \n", bslBucket, fullPrefix)
return nil
}
func (s AWSStorage) IsSnapshotExisted(cloudCredentialsFile, bslConfig, backupObject string, snapshotCheck test.SnapshotCheckPoint) error {
config := flag.NewMap()
config.Set(bslConfig)
region := config.Data()["region"]
@@ -339,26 +310,22 @@ func (s AWSStorage) IsSnapshotExisted(cloudCredentialsFile, bslConfig, backupObj
if region == "minio" {
return errors.New("No snapshot for Minio provider")
}
sess, err := s.CreateSession("", cloudCredentialsFile, "false", "", "", "", bslConfig)
cfg, err := newAWSConfig(region, "", cloudCredentialsFile, false, "")
if err != nil {
fmt.Printf("Fail to create session with profile %s and config %s", cloudCredentialsFile, bslConfig)
return errors.Wrapf(err, "Fail to create session with profile %s and config %s", cloudCredentialsFile, bslConfig)
return errors.Wrapf(err, "Failed to create AWS config of region %s", region)
}
svc := ec2.New(sess)
params := &ec2.DescribeSnapshotsInput{
OwnerIds: []*string{aws.String("self")},
Filters: []*ec2.Filter{
ec2Client := ec2.NewFromConfig(cfg)
input := &ec2.DescribeSnapshotsInput{
OwnerIds: []string{"self"},
Filters: []ec2types.Filter{
{
Name: aws.String("tag:velero.io/backup"),
Values: []*string{
aws.String(backupObject),
},
Name: aws.String("tag:velero.io/backup"),
Values: []string{backupObject},
},
},
}
result, err := svc.DescribeSnapshots(params)
result, err := ec2Client.DescribeSnapshots(context.Background(), input)
if err != nil {
fmt.Println(err)
}
@@ -386,52 +353,46 @@ func (s AWSStorage) GetMinioBucketSize(cloudCredentialsFile, bslBucket, bslPrefi
config := flag.NewMap()
config.Set(bslConfig)
region := config.Data()["region"]
s3url := config.Data()["s3Url"]
s3Config := &aws.Config{
Credentials: credentials.NewSharedCredentials(cloudCredentialsFile, ""),
Endpoint: aws.String(s3url),
Region: aws.String(region),
DisableSSL: aws.Bool(true),
S3ForcePathStyle: aws.Bool(true),
}
if region != "minio" {
return 0, errors.New("it only supported by minio")
}
sess, err := session.NewSession(s3Config)
s3url := config.Data()["s3Url"]
s3Config, err := newAWSConfig(region, "", cloudCredentialsFile, true, "")
if err != nil {
return 0, errors.Wrapf(err, "Error create config session")
return 0, errors.Wrapf(err, "failed to create AWS config of region %s", region)
}
s3Client, err := newS3Client(s3Config, s3url, true)
if err != nil {
return 0, errors.Wrapf(err, "failed to create S3 client of region %s", region)
}
/*
s3Config := &aws.Config{
Credentials: credentials.NewSharedCredentials(cloudCredentialsFile, ""),
Endpoint: aws.String(s3url),
Region: aws.String(region),
DisableSSL: aws.Bool(true),
S3ForcePathStyle: aws.Bool(true),
}
*/
svc := s3.New(sess)
var totalSize int64
var continuationToken *string
// Paginate through objects in the bucket
objectsInput := &s3.ListObjectsV2Input{
Bucket: aws.String(bslBucket),
ContinuationToken: continuationToken,
Bucket: aws.String(bslBucket),
}
if bslPrefix != "" {
objectsInput.Prefix = aws.String(bslPrefix)
}
for {
resp, err := svc.ListObjectsV2(objectsInput)
p := s3.NewListObjectsV2Paginator(s3Client, objectsInput)
for p.HasMorePages() {
page, err := p.NextPage(context.Background())
if err != nil {
return 0, errors.Wrapf(err, "Error list objects")
return 0, errors.Wrapf(err, "failed to list objects in bucket %s", bslBucket)
}
// Process objects in the current response
for _, obj := range resp.Contents {
totalSize += *obj.Size
for _, obj := range page.Contents {
totalSize += obj.Size
}
// Check if there are more objects to retrieve
if !*resp.IsTruncated {
break
}
// Set the continuation token for the next page
continuationToken = resp.NextContinuationToken
}
return totalSize, nil
}
+5 -7
View File
@@ -35,11 +35,9 @@ import (
"github.com/pkg/errors"
"golang.org/x/exp/slices"
"k8s.io/apimachinery/pkg/util/wait"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
ver "k8s.io/apimachinery/pkg/util/version"
"k8s.io/apimachinery/pkg/util/wait"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
cliinstall "github.com/vmware-tanzu/velero/pkg/cmd/cli/install"
@@ -1161,7 +1159,7 @@ func SnapshotCRsCountShouldBe(ctx context.Context, namespace, backupName string,
}
func BackupRepositoriesCountShouldBe(ctx context.Context, veleroNamespace, targetNamespace string, expectedCount int) error {
resticArr, err := GetResticRepositories(ctx, veleroNamespace, targetNamespace)
resticArr, err := GetRepositories(ctx, veleroNamespace, targetNamespace)
if err != nil {
return errors.Wrapf(err, "Fail to get BackupRepositories")
}
@@ -1172,11 +1170,11 @@ func BackupRepositoriesCountShouldBe(ctx context.Context, veleroNamespace, targe
}
}
func GetResticRepositories(ctx context.Context, veleroNamespace, targetNamespace string) ([]string, error) {
func GetRepositories(ctx context.Context, veleroNamespace, targetNamespace string) ([]string, error) {
cmds := []*common.OsCommandLine{}
cmd := &common.OsCommandLine{
Cmd: "kubectl",
Args: []string{"get", "-n", veleroNamespace, "BackupRepositories"},
Args: []string{"get", "-n", veleroNamespace, "backuprepositories.velero.io"},
}
cmds = append(cmds, cmd)