mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-21 14:46:07 +00:00
Merge branch 'main' into issue-fix-6663
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 && \
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Perf improvements for existing resource restore
|
||||
@@ -0,0 +1 @@
|
||||
Fix the node-agent missing metrics-address defines.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
Optional check if targeted container is ready before executing a hook
|
||||
@@ -0,0 +1 @@
|
||||
Bump up aws sdk to aws-sdk-go-v2
|
||||
@@ -0,0 +1 @@
|
||||
Add MSI Support for Azure plugin.
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
Import auth provider plugins
|
||||
@@ -0,0 +1 @@
|
||||
Check whether the action is a CSI action and whether CSI feature is enabled, before executing the action.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
Limit PVC block mode logic to non-Windows platform.
|
||||
@@ -0,0 +1 @@
|
||||
Fix #6988, always get region from BSL if it is not empty
|
||||
@@ -0,0 +1 @@
|
||||
Bump kopia to 0.15.0
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -1,7 +1,7 @@
|
||||
[build]
|
||||
base = "site/"
|
||||
command = "hugo --gc --minify"
|
||||
publish = "public"
|
||||
publish = "site/public"
|
||||
|
||||
[context.production.environment]
|
||||
HUGO_VERSION = "0.73.0"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)}
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user