mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-07-19 22:42:44 +00:00
Merge branch 'main' of https://github.com/qiuming-best/velero into kopia-parallelism
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Fix issue 6913: Velero Built-in Datamover: Backup stucks in phase WaitingForPluginOperations when Node Agent pod gets restarted
|
||||
@@ -0,0 +1 @@
|
||||
Support JSON Merge Patch and Strategic Merge Patch in Resource Modifiers
|
||||
@@ -0,0 +1 @@
|
||||
Bump up aws sdk to aws-sdk-go-v2
|
||||
@@ -0,0 +1 @@
|
||||
Add the design for node-agent concurrency
|
||||
@@ -0,0 +1 @@
|
||||
Add the PV backup information design document.
|
||||
@@ -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 @@
|
||||
Fix unified repository (kopia) s3 credentials profile selection
|
||||
@@ -0,0 +1 @@
|
||||
Bump kopia to 0.15.0
|
||||
@@ -0,0 +1 @@
|
||||
restore: Use warning when Create IsAlreadyExist and Get error
|
||||
@@ -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 @@
|
||||
Fix inconsistent behavior of Backup and Restore hook execution
|
||||
@@ -0,0 +1 @@
|
||||
Add HealthCheckNodePort deletion logic for Service restore.
|
||||
@@ -0,0 +1 @@
|
||||
Add description markers for dataupload and datadownload CRDs
|
||||
@@ -0,0 +1 @@
|
||||
Fix #6857. Added check for matching Owner References when synchronizing backups, removing references that are not found/have mismatched uid.
|
||||
@@ -0,0 +1 @@
|
||||
Read information from the credential specified by BSL
|
||||
@@ -0,0 +1 @@
|
||||
Fix issue #7027, data mover backup exposer should not assume the first volume as the backup volume in backup pod
|
||||
@@ -0,0 +1 @@
|
||||
Remove the Velero generated client.
|
||||
@@ -0,0 +1 @@
|
||||
Update Backup.Status.CSIVolumeSnapshotsCompleted during finalize
|
||||
@@ -0,0 +1 @@
|
||||
Remove dependency of generated client part 3.
|
||||
@@ -0,0 +1 @@
|
||||
Add the implementation for design #6950, configurable data path concurrency
|
||||
@@ -0,0 +1 @@
|
||||
Add DataUpload Result and CSI VolumeSnapshot check for restore PV.
|
||||
@@ -0,0 +1 @@
|
||||
Add VolumeInfo metadata structures.
|
||||
@@ -0,0 +1 @@
|
||||
Truncate the credential file to avoid the change of secret content messing it up
|
||||
@@ -0,0 +1 @@
|
||||
Fix issue #6693, partially fail restore if CSI snapshot is involved but CSI feature is not ready, i.e., CSI feature gate is not enabled or CSI plugin is not installed.
|
||||
@@ -0,0 +1 @@
|
||||
Skip syncing the backup which doesn't contain backup metadata
|
||||
@@ -0,0 +1 @@
|
||||
Fix issue #7068, due to an behavior of CSI external snapshotter, manipulations of VS and VSC may not be handled in the same order inside external snapshotter as the API is called. So add a protection finalizer to ensure the order
|
||||
@@ -0,0 +1 @@
|
||||
Fix issue #7094, fallback to full backup if previous snapshot is not found
|
||||
@@ -0,0 +1 @@
|
||||
Fix issue #7068, due to a behavior of CSI external snapshotter, manipulations of VS and VSC may not be handled in the same order inside external snapshotter as the API is called. So add a protection finalizer to ensure the order
|
||||
@@ -0,0 +1 @@
|
||||
Include plugin name in the error message by operations
|
||||
@@ -48,6 +48,8 @@ spec:
|
||||
name: v2alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: DataDownload acts as the protocol between data mover plugins
|
||||
and data mover controller for the datamover restore operation
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
|
||||
@@ -49,6 +49,8 @@ spec:
|
||||
name: v2alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: DataUpload acts as the protocol between data mover plugins and
|
||||
data mover controller for the datamover backup operation
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -29,7 +29,7 @@ During restore, the proposal is that Velero will determine if the `APIGroupVersi
|
||||
The proposed code starts with creating three lists for each backed up resource. The three lists will be created by
|
||||
(1) reading the directory names in the backup tarball file and seeing which API group versions were backed up from the source cluster,
|
||||
(2) looking at the target cluster and determining which API group versions are supported, and
|
||||
(3) getting config maps from the target cluster in order to get user-defined prioritization of versions.
|
||||
(3) getting ConfigMaps from the target cluster in order to get user-defined prioritization of versions.
|
||||
|
||||
The three lists will be used to create a map of chosen versions for each resource to restore. If there is a user-defined list of priority versions, the versions will be checked against the supported versions lists. The highest user-defined priority version that is/was supported by both target and source clusters will be the chosen version for that resource. If no user specified versions are supported by neither target nor source, the versions will be logged and the restore will continue with other prioritizations.
|
||||
|
||||
|
||||
@@ -88,10 +88,10 @@ Example of StrategicPatches in ResourceModifierRule
|
||||
version: v1
|
||||
resourceModifierRules:
|
||||
- conditions:
|
||||
groupResource: pods
|
||||
resourceNameRegex: "^my-pod$"
|
||||
namespaces:
|
||||
- ns1
|
||||
groupResource: pods
|
||||
resourceNameRegex: "^my-pod$"
|
||||
namespaces:
|
||||
- ns1
|
||||
strategicPatches:
|
||||
- patchData: |
|
||||
{
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# Node-agent Concurrency Design
|
||||
|
||||
## Glossary & Abbreviation
|
||||
|
||||
**Velero Generic Data Path (VGDP)**: VGDP is the collective of modules that is introduced in [Unified Repository design][1]. Velero uses these modules to finish data transfer for various purposes (i.e., PodVolume backup/restore, Volume Snapshot Data Movement). VGDP modules include uploaders and the backup repository.
|
||||
|
||||
## Background
|
||||
|
||||
Velero node-agent is a daemonset hosting controllers and VGDP modules to complete the concrete work of backups/restores, i.e., PodVolume backup/restore, Volume Snapshot Data Movement backup/restore.
|
||||
For example, node-agent runs DataUpload controllers to watch DataUpload CRs for Volume Snapshot Data Movement backups, so there is one controller instance in each node. One controller instance takes a DataUpload CR and then launches a VGDP instance, which initializes a uploader instance and the backup repository connection, to finish the data transfer. The VGDP instance runs inside the node-agent pod or in a pod associated to the node-agent pod in the same node.
|
||||
|
||||
Varying from the data size, data complexity, resource availability, VGDP may take a long time and remarkable resources (CPU, memory, network bandwidth, etc.).
|
||||
Technically, VGDP instances are able to run in concurrent regardless of the requesters. For example, a VGDP instance for a PodVolume backup could run in parallel with another VGDP instance for a DataUpload. Then the two VGDP instances share the same resources if they are running in the same node.
|
||||
|
||||
Therefore, in order to gain the optimized performance with the limited resources, it is worthy to configure the concurrent number of VGDP per node. When the resources are sufficient in nodes, users can set a large concurrent number, so as to reduce the backup/restore time; otherwise, the concurrency should be reduced, otherwise, the backup/restore may encounter problems, i.e., time lagging, hang or OOM kill.
|
||||
|
||||
## Goals
|
||||
|
||||
- Define the behaviors of concurrent VGDP instances in node-agent
|
||||
- Create a mechanism for users to specify the concurrent number of VGDP per node
|
||||
|
||||
## Non-Goals
|
||||
- VGDP instances from different nodes always run in concurrent since in most common cases the resources are isolated. For special cases that some resources are shared across nodes, there is no support at present
|
||||
- In practice, restores run in prioritized scenarios, e.g., disaster recovery. However, the current design doesn't consider this difference, a VGDP instance for a restore is blocked if it reaches to the limit of the concurrency, even though the ones block it are for backups. If users do meet some problems here, they should consider to stop the backups first
|
||||
- Sometimes, users wants to totally block backups/restores from running in a specific node, this is out of the scope the current design. To archive this, more modules need to be considered (i.e., expoers of data movers), simply blocking the VGDP (e.g., by setting its concurrent number to 0) doesn't work. E.g., for a fs backup, VGDP instance must run in the node the source pod is running in, if we simply block from VGDP instance, the PodVolumeBackup CR is still submitted but never processed.
|
||||
|
||||
## Solution
|
||||
|
||||
We introduce a configMap named ```node-agent-configs``` for users to specify the node-agent related configurations. This configMap is not created by Velero, users should create it manually on demand. The configMap should be in the same namespace where Velero is installed. If multiple Velero instances are installed in different namespaces, there should be one configMap in each namespace which applies to node-agent in that namespace only.
|
||||
Node-agent server checks these configurations at startup time and use it to initiate the related VGDP modules. Therefore, users could edit this configMap any time, but in order to make the changes effective, node-agent server needs to be restarted.
|
||||
The ```node-agent-configs``` configMap may be used for other purpose of configuring node-agent in future, at present, there is only one kind of configuration as the data in the configMap, the name is ```dataPathConcurrency```.
|
||||
|
||||
The data structure for ```node-agent-configs``` is as below:
|
||||
```go
|
||||
type Configs struct {
|
||||
// DataPathConcurrency is the config for data path concurrency per node.
|
||||
DataPathConcurrency *DataPathConcurrency `json:"dataPathConcurrency,omitempty"`
|
||||
}
|
||||
|
||||
type DataPathConcurrency struct {
|
||||
// GlobalConfig specifies the concurrency number to all nodes for which per-node config is not specified
|
||||
GlobalConfig int `json:"globalConfig,omitempty"`
|
||||
|
||||
// PerNodeConfig specifies the concurrency number to nodes matched by rules
|
||||
PerNodeConfig []RuledConfigs `json:"perNodeConfig,omitempty"`
|
||||
}
|
||||
|
||||
type RuledConfigs struct {
|
||||
// NodeSelector specifies the label selector to match nodes
|
||||
NodeSelector metav1.LabelSelector `json:"nodeSelector"`
|
||||
|
||||
// Number specifies the number value associated to the matched nodes
|
||||
Number int `json:"number"`
|
||||
}
|
||||
```
|
||||
|
||||
### Global concurrent number
|
||||
We allow users to specify a concurrent number that will be applied to all nodes if the per-node number is not specified. This number is set through ```globalConfig``` field in ```dataPathConcurrency```.
|
||||
The number starts from 1 which means there is no concurrency, only one instance of VGDP is allowed. There is no roof limit.
|
||||
If this number is not specified or not valid, a hard-coded default value will be used, the value is set to 1.
|
||||
|
||||
### Per-node concurrent number
|
||||
We allow users to specify different concurrent number per node, for example, users can set 3 concurrent instances in Node-1, 2 instances in Node-2 and 1 instance in Node-3. This is for below considerations:
|
||||
- The resources may be different among nodes. Then users could specify smaller concurrent number for nodes with less resources while larger number for the ones with more resources
|
||||
- Help users to isolate critical environments. Users may run some critical workloads in some specified nodes, since VGDP instances may take large resource consumption, users may want to run less number of instances in the nodes with critical workloads
|
||||
|
||||
The range of Per-node concurrent number is the same with Global concurrent number.
|
||||
Per-node concurrent number is preferable to Global concurrent number, so it will overwrite the Global concurrent number for that node.
|
||||
|
||||
Per-node concurrent number is implemented through ```perNodeConfig``` field in ```dataPathConcurrency```.
|
||||
|
||||
```perNodeConfig``` is a list of ```RuledConfigs``` each item of which matches one or more nodes by label selectors and specify the concurrent number for the matched nodes. This means, the nodes are identified by labels.
|
||||
|
||||
For example, the ```perNodeConfig`` could have below elements:
|
||||
```
|
||||
"nodeSelector: kubernetes.io/hostname=node1; number: 3"
|
||||
"nodeSelector: beta.kubernetes.io/instance-type=Standard_B4ms; number: 5"
|
||||
```
|
||||
The first element means the node with host name ```node1``` gets the Per-node concurrent number of 3.
|
||||
The second element means all the nodes with label ```beta.kubernetes.io/instance-type``` of value ```Standard_B4ms``` get the Per-node concurrent number of 5.
|
||||
At least one node is expected to have a label with the specified ```RuledConfigs``` element (rule). If no node is with this label, the Per-node rule makes no effect.
|
||||
If one node falls into more than one rules, e.g., if node1 also has the label ```beta.kubernetes.io/instance-type=Standard_B4ms```, the smallest number (3) will be used.
|
||||
|
||||
### Sample
|
||||
A sample of the ```node-agent-configs``` configMap is as below:
|
||||
```json
|
||||
{
|
||||
"dataPathConcurrency": {
|
||||
"globalConfig": 2,
|
||||
"perNodeConfig": [
|
||||
{
|
||||
"nodeSelector": {
|
||||
"matchLabels": {
|
||||
"kubernetes.io/hostname": "node1"
|
||||
}
|
||||
},
|
||||
"number": 3
|
||||
},
|
||||
{
|
||||
"nodeSelector": {
|
||||
"matchLabels": {
|
||||
"beta.kubernetes.io/instance-type": "Standard_B4ms"
|
||||
}
|
||||
},
|
||||
"number": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
To create the configMap, users need to save something like the above sample to a json file and then run below command:
|
||||
```
|
||||
kubectl create cm node-agent-configs -n velero --from-file=<json file name>
|
||||
```
|
||||
|
||||
### Global data path manager
|
||||
As for the code implementation, data path manager is to maintain the total number of the running VGDP instances and ensure the limit is not excceeded. At present, there is one data path manager instance per controller, as a result, the concurrent numbers are calculated separately for each controller. This doesn't help to limit the concurrency among different requesters.
|
||||
Therefore, we need to create one global data path manager instance server-wide, and pass it to different controllers. The instance will be created at node-agent server startup.
|
||||
The concurrent number is required to initiate a data path manager, the number comes from either Per-node concurrent number or Global concurrent number.
|
||||
Below are some prototypes related to data path manager:
|
||||
|
||||
```go
|
||||
func NewManager(cocurrentNum int) *Manager
|
||||
func (m *Manager) CreateFileSystemBR(jobName string, requestorType string, ctx context.Context, client client.Client, namespace string, callbacks Callbacks, log logrus.FieldLogger) (AsyncBR, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[1]: Implemented/unified-repo-and-kopia-integration/unified-repo-and-kopia-integration.md
|
||||
@@ -0,0 +1,186 @@
|
||||
# PersistentVolume backup information design
|
||||
|
||||
## Abstract
|
||||
Create a new metadata file in the backup repository's backup name sub-directory to store the backup-including PVC and PV information. The information includes the way of backing up the PVC and PV data, snapshot information, and status. The needed snapshot status can also be recorded there, but the Velero-Native snapshot plugin doesn't provide a way to get the snapshot size from the API, so it's possible that not all snapshot size information is available.
|
||||
|
||||
This new additional metadata file is needed when:
|
||||
* Get a summary of the backup's PVC and PV information, including how the data in them is backed up, or whether the data in them is skipped from backup.
|
||||
* Find out how the PVC and PV should be restored in the restore process.
|
||||
* Retrieve the PV's snapshot information for backup.
|
||||
|
||||
## Background
|
||||
There is already a [PR](https://github.com/vmware-tanzu/velero/pull/6496) to track the skipped PVC in the backup. This design will depend on it and go further to get a summary of PVC and PV information, then persist into a metadata file in the backup repository.
|
||||
|
||||
In the restore process, the Velero server needs to decide how the PV resource should be restored according to how the PV is backed up. The current logic is to check whether it's backed up by Velero-native snapshot, by file-system backup, or having `DeletionPolicy` set as `Delete`.
|
||||
|
||||
The checks are made by the backup-generated PVBs or Snapshots. There is no generic way to find this information, and the CSI backup and Snapshot data movement backup are not covered.
|
||||
|
||||
Another thing that needs noticing is when describing the backup, there is no generic way to find the PV's snapshot information.
|
||||
|
||||
## Goals
|
||||
- Create a new metadata file to store backup's PVCs and PVs information and volume data backing up method. The file can be used to let downstream consumers generate a summary.
|
||||
- Create a generic way to let the Velero server know how the PV resources are backed up.
|
||||
- Create a generic way to let the Velero server find the PV corresponding snapshot information.
|
||||
|
||||
## Non Goals
|
||||
- Unify how to get snapshot size information for all PV backing-up methods, and all other currently not ready PVs' information.
|
||||
|
||||
## High-Level Design
|
||||
Create _backup-name_-volumes-info.json metadata file in the backup's repository. This file will be encoded to contain all the PVC and PV information included in the backup. The information covers whether the PV or PVC's data is skipped during backup, how its data is backed up, and the backed-up detail information.
|
||||
|
||||
Please notice that the new metadata file includes all skipped volume information. This is used to address [the second phase needs of skipped volumes information](https://github.com/vmware-tanzu/velero/issues/5834#issuecomment-1526624211).
|
||||
|
||||
The `restoreItem` function can decode the _backup-name_-volumes-info.json file to determine how to handle the PV resource.
|
||||
|
||||
## Detailed Design
|
||||
|
||||
### The VolumeInfo structure
|
||||
_backup-name_-volumes-info.json file is a structure that contains an array of structure `VolumeInfo`.
|
||||
|
||||
``` golang
|
||||
type VolumeInfo struct {
|
||||
PVCName string // The PVC's name.
|
||||
PVCNamespace string // The PVC's namespace.
|
||||
PVName string // The PV name.
|
||||
BackupMethod string // The way the volume data is backed up. The valid value includes `VeleroNativeSnapshot`, `PodVolumeBackup` and `CSISnapshot`.
|
||||
SnapshotDataMoved bool // Whether the volume's snapshot data is moved to specified storage.
|
||||
|
||||
Skipped boolean // Whether the Volume is skipped in this backup.
|
||||
SkippedReason string // The reason for the volume is skipped in the backup.
|
||||
StartTimestamp *metav1.Time // Snapshot starts timestamp.
|
||||
|
||||
OperationID string // The Async Operation's ID.
|
||||
|
||||
CSISnapshotInfo CSISnapshotInfo
|
||||
SnapshotDataMovementInfo SnapshotDataMovementInfo
|
||||
NativeSnapshotInfo VeleroNativeSnapshotInfo
|
||||
PVBInfo PodVolumeBackupInfo
|
||||
PVInfo PVInfo
|
||||
}
|
||||
|
||||
// CSISnapshotInfo is used for displaying the CSI snapshot status
|
||||
type CSISnapshotInfo struct {
|
||||
SnapshotHandle string // It's the storage provider's snapshot ID for CSI.
|
||||
Size int64 // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot.
|
||||
|
||||
Driver string // The name of the CSI driver.
|
||||
VSCName string // The name of the VolumeSnapshotContent.
|
||||
}
|
||||
|
||||
// SnapshotDataMovementInfo is used for displaying the snapshot data mover status.
|
||||
type SnapshotDataMovementInfo struct {
|
||||
DataMover string // The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`).
|
||||
UploaderType string // The type of the uploader that uploads the snapshot data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover.
|
||||
RetainedSnapshot string // The name or ID of the snapshot associated object(SAO). SAO is used to support local snapshots for the snapshot data mover, e.g. it could be a VolumeSnapshot for CSI snapshot data moign/pv_backup_info.
|
||||
SnapshotHandle string // It's the filesystem repository's snapshot ID.
|
||||
|
||||
}
|
||||
|
||||
// VeleroNativeSnapshotInfo is used for displaying the Velero native snapshot status.
|
||||
type VeleroNativeSnapshotInfo struct {
|
||||
SnapshotHandle string // It's the storage provider's snapshot ID for the Velero-native snapshot.
|
||||
Size int64 // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot.
|
||||
|
||||
VolumeType string // The cloud provider snapshot volume type.
|
||||
VolumeAZ string // The cloud provider snapshot volume's availability zones.
|
||||
IOPS string // The cloud provider snapshot volume's IOPS.
|
||||
}
|
||||
|
||||
// PodVolumeBackupInfo is used for displaying the PodVolumeBackup snapshot status.
|
||||
type PodVolumeBackupInfo struct {
|
||||
SnapshotHandle string // It's the file-system uploader's snapshot ID for PodVolumeBackup.
|
||||
Size int64 // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot.
|
||||
|
||||
UploaderType string // The type of the uploader that uploads the data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover.
|
||||
VolumeName string // The PVC's corresponding volume name used by Pod: https://github.com/kubernetes/kubernetes/blob/e4b74dd12fa8cb63c174091d5536a10b8ec19d34/pkg/apis/core/types.go#L48
|
||||
PodName string // The Pod name mounting this PVC. The format should be <namespace-name>/<pod-name>.
|
||||
NodeName string // The PVB-taken k8s node's name.
|
||||
}
|
||||
|
||||
// PVInfo is used to store some PV information modified after creation.
|
||||
// Those information are lost after PV recreation.
|
||||
type PVInfo struct {
|
||||
ReclaimPolicy string // ReclaimPolicy of PV. It could be different from the referenced StorageClass.
|
||||
Labels map[string]string // The PV's labels should be kept after recreation.
|
||||
}
|
||||
```
|
||||
|
||||
### How the VolumeInfo array is generated.
|
||||
The function `persistBackup` has `backup *pkgbackup.Request` in parameters.
|
||||
From it, the `VolumeSnapshots`, `PodVolumeBackups`, `CSISnapshots`, `itemOperationsList`, and `SkippedPVTracker` can be read. All of them will be iterated and merged into the `VolumeInfo` array, and then persisted into backup repository in function `persistBackup`.
|
||||
|
||||
Please notice that the change happened in async operations are not reflected in the new metadata file. The file only covers the volume changes happen in the Velero server process scope.
|
||||
|
||||
A new methods are added to BackupStore to download the VolumeInfo metadata file.
|
||||
Uploading the metadata file is covered in the exiting `PutBackup` method.
|
||||
|
||||
``` golang
|
||||
type BackupStore interface {
|
||||
...
|
||||
GetVolumeInfos(name string) ([]*VolumeInfo, error)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### How the VolumeInfo array is used.
|
||||
|
||||
#### Generate the PVC backed-up information summary
|
||||
The downstream tools can use this VolumeInfo array to format and display their volume information. This is in the scope of this feature.
|
||||
|
||||
#### Retrieve volume backed-up information for `velero backup describe` command
|
||||
The `velero backup describe` can also use this VolumeInfo array structure to display the volume information. The snapshot data mover volume should use this structure at first, then the Velero native snapshot, CSI snapshot, and PodVolumeBackup can also use this structure. The detailed implementation is also not in this feature's scope.
|
||||
|
||||
#### Let restore know how to restore the PV
|
||||
In the function `restoreItem`, it will determine whether to restore the PV resource by checking it in the Velero native snapshots list, PodVolumeBackup list, and its DeletionPolicy. This logic is still kept. The logic will be used when the new `VolumeInfo` metadata cannot be found to support backward compatibility.
|
||||
|
||||
``` golang
|
||||
if groupResource == kuberesource.PersistentVolumes {
|
||||
switch {
|
||||
case hasSnapshot(name, ctx.volumeSnapshots):
|
||||
...
|
||||
case hasPodVolumeBackup(obj, ctx):
|
||||
...
|
||||
case hasDeleteReclaimPolicy(obj.Object):
|
||||
...
|
||||
default:
|
||||
...
|
||||
```
|
||||
|
||||
After introducing the VolumeInfo array, the following logic will be added.
|
||||
``` golang
|
||||
if groupResource == kuberesource.PersistentVolumes {
|
||||
volumeInfo := GetVolumeInfo(pvName)
|
||||
switch volumeInfo.BackupMethod {
|
||||
case VeleroNativeSnapshot:
|
||||
...
|
||||
case PodVolumeBackup:
|
||||
...
|
||||
case CSISnapshot:
|
||||
...
|
||||
default:
|
||||
// Need to check whether the volume is backed up by the SnapshotDataMover.
|
||||
if volumeInfo.SnapshotDataMovement:
|
||||
|
||||
// Check whether the Velero server should restore the PV depending on the DeletionPolicy setting.
|
||||
if volumeInfo.Skipped:
|
||||
```
|
||||
|
||||
### How the VolumeInfo metadata file is deleted
|
||||
_backup-name_-volumes-info.json file is deleted during backup deletion.
|
||||
|
||||
## Alternatives Considered
|
||||
The restore process needs more information about how the PVs are backed up to determine whether this PV should be restored. The released branches also need a similar function, but backporting a new feature into previous releases may not be a good idea, so according to [Anshul Ahuja's suggestion](https://github.com/vmware-tanzu/velero/issues/6595#issuecomment-1731081580), adding more cases here to support checking PV backed-up by CSI plugin and CSI snapshot data mover: https://github.com/vmware-tanzu/velero/blob/5ff5073cc3f364bafcfbd26755e2a92af68ba180/pkg/restore/restore.go#L1206-L1324.
|
||||
|
||||
## Security Considerations
|
||||
There should be no security impact introduced by this design.
|
||||
|
||||
## Compatibility
|
||||
After this design is implemented, there should be no impact on the existing [skipped PVC summary feature](https://github.com/vmware-tanzu/velero/pull/6496).
|
||||
|
||||
To support older version backup, which doesn't have the VolumeInfo metadata file, the old logic, which is checking the Velero native snapshots list, PodVolumeBackup list, and PVC DeletionPolicy, is still kept, and supporting CSI snapshots and snapshot data mover logic will be added too.
|
||||
|
||||
## Implementation
|
||||
This will be implemented in the Velero v1.13 development cycle.
|
||||
|
||||
## Open Issues
|
||||
There are no open issues identified by now.
|
||||
@@ -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.
|
||||
@@ -626,10 +626,9 @@ Therefore, we have below principles:
|
||||
|
||||
We will address the two principles step by step. As the first step, VBDM’s parallelism is designed as below:
|
||||
- We don’t create the load balancing mechanism for the first step, we don’t detect the accessibility of the volume/volume snapshot explicitly. Instead, we create the backupPod/restorePod under the help of Kubernetes, Kubernetes schedules the backupPod/restorePod to the appropriate node, then the data movement controller on that node will handle the DataUpload/DataDownload CR there, so the resource will be consumed from that node.
|
||||
- We don’t expose the configurable concurrency value in one node, instead, the concurrency value in value will be set to 1, that is, there is no concurrency in one node.
|
||||
- We expose the configurable concurrency value per node, for details of how the concurrency number constraints various backups and restores which share VGDP, check the [node-agent concurrency design][3].
|
||||
|
||||
As for the resource consumption, it is related to the data scale of the data movement activity and it is charged to node-agent pods, so users should configure enough resource to node-agent pods.
|
||||
Meanwhile, Pod Volume Backup/Restore are also running in node-agent pods, we don’t restrict the concurrency of these two types. For example, in one node, one Pod Volume Backup and one DataUpload could run at the same time, in this case, the resource will be shared by the two activities.
|
||||
|
||||
## Progress Report
|
||||
When a DUCR/DDCR is in InProgress phase, users could check the progress.
|
||||
@@ -666,6 +665,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
|
||||
@@ -967,5 +969,6 @@ Restore command is kept as is.
|
||||
|
||||
|
||||
|
||||
[1]: ../unified-repo-and-kopia-integration/unified-repo-and-kopia-integration.md
|
||||
[2]: ../general-progress-monitoring.md
|
||||
[1]: ../Implemented/unified-repo-and-kopia-integration/unified-repo-and-kopia-integration.md
|
||||
[2]: ../Implemented/general-progress-monitoring.md
|
||||
[3]: ../node-agent-concurrency.md
|
||||
@@ -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/mod v0.13.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.11.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
|
||||
@@ -60,11 +64,12 @@ require (
|
||||
k8s.io/metrics v0.25.6
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
|
||||
sigs.k8s.io/controller-runtime v0.12.2
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
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 +82,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 +119,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 +129,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,38 +155,37 @@ 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.14.0 // indirect
|
||||
golang.org/x/sync v0.3.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
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/component-base v0.24.2 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
)
|
||||
|
||||
replace github.com/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,7 +880,6 @@ 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.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=
|
||||
@@ -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,8 +977,6 @@ 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.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=
|
||||
@@ -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,11 +1085,8 @@ 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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
@@ -1072,7 +1095,6 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
|
||||
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.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=
|
||||
@@ -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=
|
||||
@@ -1410,8 +1428,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lR
|
||||
sigs.k8s.io/controller-runtime v0.12.2 h1:nqV02cvhbAj7tbt21bpPpTByrXGn2INHRsi39lXy9sE=
|
||||
sigs.k8s.io/controller-runtime v0.12.2/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0=
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g=
|
||||
sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI=
|
||||
sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM=
|
||||
|
||||
@@ -19,7 +19,7 @@ HACK_DIR=$(dirname "${BASH_SOURCE}")
|
||||
${HACK_DIR}/update-3generated-crd-code.sh
|
||||
|
||||
# ensure no changes to generated CRDs
|
||||
if [! git diff --exit-code config/crd/v1/crds/crds.go config/crd/v2alpha1/crds/crds.go >/dev/null]; then
|
||||
if ! git diff --exit-code config/crd/v1/crds/crds.go config/crd/v2alpha1/crds/crds.go &> /dev/null; then
|
||||
# revert changes to state before running CRD generation to stay consistent
|
||||
# with code-generator `--verify-only` option which discards generated changes
|
||||
git checkout config/crd
|
||||
|
||||
@@ -71,7 +71,7 @@ func (n *namespacedFileStore) Path(selector *corev1api.SecretKeySelector) (strin
|
||||
|
||||
keyFilePath := filepath.Join(n.fsRoot, fmt.Sprintf("%s-%s", selector.Name, selector.Key))
|
||||
|
||||
file, err := n.fs.OpenFile(keyFilePath, os.O_RDWR|os.O_CREATE, 0644)
|
||||
file, err := n.fs.OpenFile(keyFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "unable to open credentials file for writing")
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -50,6 +50,11 @@ type DefaultListWatchFactory struct {
|
||||
PodsGetter cache.Getter
|
||||
}
|
||||
|
||||
type HookErrInfo struct {
|
||||
Namespace string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (d *DefaultListWatchFactory) NewListWatch(namespace string, selector fields.Selector) cache.ListerWatcher {
|
||||
return cache.NewListWatchFromClient(d.PodsGetter, "pods", namespace, selector)
|
||||
}
|
||||
@@ -158,8 +163,8 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
if hook.Hook.WaitTimeout.Duration != 0 && time.Since(waitStart) > hook.Hook.WaitTimeout.Duration {
|
||||
err := fmt.Errorf("hook %s in container %s expired before executing", hook.HookName, hook.Hook.Container)
|
||||
hookLog.Error(err)
|
||||
errors = append(errors, err)
|
||||
if hook.Hook.OnError == velerov1api.HookErrorModeFail {
|
||||
errors = append(errors, err)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
@@ -172,8 +177,9 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
}
|
||||
if err := e.PodCommandExecutor.ExecutePodCommand(hookLog, podMap, pod.Namespace, pod.Name, hook.HookName, eh); err != nil {
|
||||
hookLog.WithError(err).Error("Error executing hook")
|
||||
err = fmt.Errorf("hook %s in container %s failed to execute, err: %v", hook.HookName, hook.Hook.Container, err)
|
||||
errors = append(errors, err)
|
||||
if hook.Hook.OnError == velerov1api.HookErrorModeFail {
|
||||
errors = append(errors, err)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
@@ -204,10 +210,9 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
podWatcher.Run(ctx.Done())
|
||||
|
||||
// There are some cases where this function could return with unexecuted hooks: the pod may
|
||||
// be deleted, a hook with OnError mode Fail could fail, or it may timeout waiting for
|
||||
// be deleted, a hook could fail, or it may timeout waiting for
|
||||
// containers to become ready.
|
||||
// Each unexecuted hook is logged as an error but only hooks with OnError mode Fail return
|
||||
// an error from this function.
|
||||
// Each unexecuted hook is logged as an error and this error will be returned from this function.
|
||||
for _, hooks := range byContainer {
|
||||
for _, hook := range hooks {
|
||||
if hook.executed {
|
||||
@@ -222,9 +227,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
},
|
||||
)
|
||||
hookLog.Error(err)
|
||||
if hook.Hook.OnError == velerov1api.HookErrorModeFail {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -209,10 +209,10 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
||||
Result(),
|
||||
},
|
||||
},
|
||||
expectedErrors: []error{errors.New("pod hook error")},
|
||||
expectedErrors: []error{errors.New("hook <from-annotation> in container container1 failed to execute, err: pod hook error")},
|
||||
},
|
||||
{
|
||||
name: "should return no error when hook from annotation fails with on error mode continue",
|
||||
name: "should return error when hook from annotation fails with on error mode continue",
|
||||
initialPod: builder.ForPod("default", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations(
|
||||
podRestoreHookCommandAnnotationKey, "/usr/bin/foo",
|
||||
@@ -278,7 +278,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
||||
Result(),
|
||||
},
|
||||
},
|
||||
expectedErrors: nil,
|
||||
expectedErrors: []error{errors.New("hook <from-annotation> in container container1 failed to execute, err: pod hook error")},
|
||||
},
|
||||
{
|
||||
name: "should return no error when hook from annotation executes after 10ms wait for container to start",
|
||||
@@ -422,7 +422,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should return no error when spec hook with wait timeout expires with OnError mode Continue",
|
||||
name: "should return error when spec hook with wait timeout expires with OnError mode Continue",
|
||||
groupResource: "pods",
|
||||
initialPod: builder.ForPod("default", "my-pod").
|
||||
Containers(&v1.Container{
|
||||
@@ -435,7 +435,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
||||
},
|
||||
}).
|
||||
Result(),
|
||||
expectedErrors: nil,
|
||||
expectedErrors: []error{errors.New("hook my-hook-1 in container container1 in pod default/my-pod not executed: context deadline exceeded")},
|
||||
byContainer: map[string][]PodExecRestoreHook{
|
||||
"container1": {
|
||||
{
|
||||
@@ -515,8 +515,8 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
||||
sharedHooksContextTimeout: time.Millisecond,
|
||||
},
|
||||
{
|
||||
name: "should return no error when shared hooks context is canceled before spec hook with OnError mode Continue executes",
|
||||
expectedErrors: nil,
|
||||
name: "should return error when shared hooks context is canceled before spec hook with OnError mode Continue executes",
|
||||
expectedErrors: []error{errors.New("hook my-hook-1 in container container1 in pod default/my-pod not executed: context deadline exceeded")},
|
||||
groupResource: "pods",
|
||||
initialPod: builder.ForPod("default", "my-pod").
|
||||
Containers(&v1.Container{
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package resourcemodifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type JSONMergePatch struct {
|
||||
PatchData string `json:"patchData,omitempty"`
|
||||
}
|
||||
|
||||
type JSONMergePatcher struct {
|
||||
patches []JSONMergePatch
|
||||
}
|
||||
|
||||
func (p *JSONMergePatcher) Patch(u *unstructured.Unstructured, _ logrus.FieldLogger) (*unstructured.Unstructured, error) {
|
||||
objBytes, err := u.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in marshaling object %s", err)
|
||||
}
|
||||
|
||||
for _, patch := range p.patches {
|
||||
patchBytes, err := yaml.YAMLToJSON([]byte(patch.PatchData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in converting YAML to JSON %s", err)
|
||||
}
|
||||
|
||||
objBytes, err = jsonpatch.MergePatch(objBytes, patchBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in applying JSON Patch: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
updated := &unstructured.Unstructured{}
|
||||
err = updated.UnmarshalJSON(objBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in unmarshalling modified object %s", err.Error())
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package resourcemodifiers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func TestJsonMergePatchFailure(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data string
|
||||
}{
|
||||
{
|
||||
name: "patch with bad yaml",
|
||||
data: "a: b:",
|
||||
},
|
||||
{
|
||||
name: "patch with bad json",
|
||||
data: `{"a"::1}`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
err := clientgoscheme.AddToScheme(scheme)
|
||||
assert.NoError(t, err)
|
||||
pt := &JSONMergePatcher{
|
||||
patches: []JSONMergePatch{{PatchData: tt.data}},
|
||||
}
|
||||
|
||||
u := &unstructured.Unstructured{}
|
||||
_, err = pt.Patch(u, logrus.New())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package resourcemodifiers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
type JSONPatch struct {
|
||||
Operation string `json:"operation"`
|
||||
From string `json:"from,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (p *JSONPatch) ToString() string {
|
||||
if addQuotes(p.Value) {
|
||||
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": "%s"}`, p.Operation, p.From, p.Path, p.Value)
|
||||
}
|
||||
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": %s}`, p.Operation, p.From, p.Path, p.Value)
|
||||
}
|
||||
|
||||
func addQuotes(value string) bool {
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
// if value is null, then don't add quotes
|
||||
if value == "null" {
|
||||
return false
|
||||
}
|
||||
// if value is a boolean, then don't add quotes
|
||||
if _, err := strconv.ParseBool(value); err == nil {
|
||||
return false
|
||||
}
|
||||
// if value is a json object or array, then don't add quotes.
|
||||
if strings.HasPrefix(value, "{") || strings.HasPrefix(value, "[") {
|
||||
return false
|
||||
}
|
||||
// if value is a number, then don't add quotes
|
||||
if _, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type JSONPatcher struct {
|
||||
patches []JSONPatch `yaml:"patches"`
|
||||
}
|
||||
|
||||
func (p *JSONPatcher) Patch(u *unstructured.Unstructured, logger logrus.FieldLogger) (*unstructured.Unstructured, error) {
|
||||
modifiedObjBytes, err := p.applyPatch(u)
|
||||
if err != nil {
|
||||
if errors.Is(err, jsonpatch.ErrTestFailed) {
|
||||
logger.Infof("Test operation failed for JSON Patch %s", err.Error())
|
||||
return u.DeepCopy(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("error in applying JSON Patch %s", err.Error())
|
||||
}
|
||||
|
||||
updated := &unstructured.Unstructured{}
|
||||
err = updated.UnmarshalJSON(modifiedObjBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in unmarshalling modified object %s", err.Error())
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (p *JSONPatcher) applyPatch(u *unstructured.Unstructured) ([]byte, error) {
|
||||
patchBytes := p.patchArrayToByteArray()
|
||||
jsonPatch, err := jsonpatch.DecodePatch(patchBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in decoding json patch %s", err.Error())
|
||||
}
|
||||
|
||||
objBytes, err := u.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in marshaling object %s", err.Error())
|
||||
}
|
||||
|
||||
return jsonPatch.Apply(objBytes)
|
||||
}
|
||||
|
||||
func (p *JSONPatcher) patchArrayToByteArray() []byte {
|
||||
var patches []string
|
||||
for _, patch := range p.patches {
|
||||
patches = append(patches, patch.ToString())
|
||||
}
|
||||
patchesStr := strings.Join(patches, ",\n\t")
|
||||
return []byte(fmt.Sprintf(`[%s]`, patchesStr))
|
||||
}
|
||||
@@ -18,16 +18,16 @@ package resourcemodifiers
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
@@ -38,11 +38,9 @@ const (
|
||||
ResourceModifierSupportedVersionV1 = "v1"
|
||||
)
|
||||
|
||||
type JSONPatch struct {
|
||||
Operation string `json:"operation"`
|
||||
From string `json:"from,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Value string `json:"value,omitempty"`
|
||||
type MatchRule struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type Conditions struct {
|
||||
@@ -50,11 +48,14 @@ type Conditions struct {
|
||||
GroupResource string `json:"groupResource"`
|
||||
ResourceNameRegex string `json:"resourceNameRegex,omitempty"`
|
||||
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
|
||||
Matches []MatchRule `json:"matches,omitempty"`
|
||||
}
|
||||
|
||||
type ResourceModifierRule struct {
|
||||
Conditions Conditions `json:"conditions"`
|
||||
Patches []JSONPatch `json:"patches"`
|
||||
Conditions Conditions `json:"conditions"`
|
||||
Patches []JSONPatch `json:"patches,omitempty"`
|
||||
MergePatches []JSONMergePatch `json:"mergePatches,omitempty"`
|
||||
StrategicPatches []StrategicMergePatch `json:"strategicPatches,omitempty"`
|
||||
}
|
||||
|
||||
type ResourceModifiers struct {
|
||||
@@ -83,10 +84,10 @@ func GetResourceModifiersFromConfig(cm *v1.ConfigMap) (*ResourceModifiers, error
|
||||
return resModifiers, nil
|
||||
}
|
||||
|
||||
func (p *ResourceModifiers) ApplyResourceModifierRules(obj *unstructured.Unstructured, groupResource string, log logrus.FieldLogger) []error {
|
||||
func (p *ResourceModifiers) ApplyResourceModifierRules(obj *unstructured.Unstructured, groupResource string, scheme *runtime.Scheme, log logrus.FieldLogger) []error {
|
||||
var errs []error
|
||||
for _, rule := range p.ResourceModifierRules {
|
||||
err := rule.Apply(obj, groupResource, log)
|
||||
err := rule.apply(obj, groupResource, scheme, log)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
@@ -95,13 +96,22 @@ func (p *ResourceModifiers) ApplyResourceModifierRules(obj *unstructured.Unstruc
|
||||
return errs
|
||||
}
|
||||
|
||||
func (r *ResourceModifierRule) Apply(obj *unstructured.Unstructured, groupResource string, log logrus.FieldLogger) error {
|
||||
namespaceInclusion := collections.NewIncludesExcludes().Includes(r.Conditions.Namespaces...)
|
||||
if !namespaceInclusion.ShouldInclude(obj.GetNamespace()) {
|
||||
return nil
|
||||
func (r *ResourceModifierRule) apply(obj *unstructured.Unstructured, groupResource string, scheme *runtime.Scheme, log logrus.FieldLogger) error {
|
||||
ns := obj.GetNamespace()
|
||||
if ns != "" {
|
||||
namespaceInclusion := collections.NewIncludesExcludes().Includes(r.Conditions.Namespaces...)
|
||||
if !namespaceInclusion.ShouldInclude(ns) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if r.Conditions.GroupResource != groupResource {
|
||||
g, err := glob.Compile(r.Conditions.GroupResource, '.')
|
||||
if err != nil {
|
||||
log.Errorf("Bad glob pattern of groupResource in condition, groupResource: %s, err: %s", r.Conditions.GroupResource, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !g.Match(groupResource) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -125,87 +135,82 @@ func (r *ResourceModifierRule) Apply(obj *unstructured.Unstructured, groupResour
|
||||
}
|
||||
}
|
||||
|
||||
patches, err := r.PatchArrayToByteArray()
|
||||
match, err := matchConditions(obj, r.Conditions.Matches, log)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !match {
|
||||
log.Info("Conditions do not match, skip it")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("Applying resource modifier patch on %s/%s", obj.GetNamespace(), obj.GetName())
|
||||
err = ApplyPatch(patches, obj, log)
|
||||
err = r.applyPatch(obj, scheme, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PatchArrayToByteArray converts all JsonPatch to string array with the format of jsonpatch.Patch and then convert it to byte array
|
||||
func (r *ResourceModifierRule) PatchArrayToByteArray() ([]byte, error) {
|
||||
var patches []string
|
||||
for _, patch := range r.Patches {
|
||||
patches = append(patches, patch.ToString())
|
||||
func matchConditions(u *unstructured.Unstructured, rules []MatchRule, _ logrus.FieldLogger) (bool, error) {
|
||||
if len(rules) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
patchesStr := strings.Join(patches, ",\n\t")
|
||||
return []byte(fmt.Sprintf(`[%s]`, patchesStr)), nil
|
||||
}
|
||||
|
||||
func (p *JSONPatch) ToString() string {
|
||||
if addQuotes(p.Value) {
|
||||
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": "%s"}`, p.Operation, p.From, p.Path, p.Value)
|
||||
}
|
||||
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": %s}`, p.Operation, p.From, p.Path, p.Value)
|
||||
}
|
||||
var fixed []JSONPatch
|
||||
for _, rule := range rules {
|
||||
if rule.Path == "" {
|
||||
return false, fmt.Errorf("path is required for match rule")
|
||||
}
|
||||
|
||||
func ApplyPatch(patch []byte, obj *unstructured.Unstructured, log logrus.FieldLogger) error {
|
||||
jsonPatch, err := jsonpatch.DecodePatch(patch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in decoding json patch %s", err.Error())
|
||||
fixed = append(fixed, JSONPatch{
|
||||
Operation: "test",
|
||||
Path: rule.Path,
|
||||
Value: rule.Value,
|
||||
})
|
||||
}
|
||||
objBytes, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in marshaling object %s", err.Error())
|
||||
}
|
||||
modifiedObjBytes, err := jsonPatch.Apply(objBytes)
|
||||
|
||||
p := &JSONPatcher{patches: fixed}
|
||||
_, err := p.applyPatch(u)
|
||||
if err != nil {
|
||||
if errors.Is(err, jsonpatch.ErrTestFailed) {
|
||||
log.Infof("Test operation failed for JSON Patch %s", err.Error())
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
return fmt.Errorf("error in applying JSON Patch %s", err.Error())
|
||||
return false, err
|
||||
}
|
||||
err = obj.UnmarshalJSON(modifiedObjBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in unmarshalling modified object %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func unmarshalResourceModifiers(yamlData []byte) (*ResourceModifiers, error) {
|
||||
resModifiers := &ResourceModifiers{}
|
||||
err := yaml.UnmarshalStrict(yamlData, resModifiers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode yaml data into resource modifiers %v", err)
|
||||
return nil, fmt.Errorf("failed to decode yaml data into resource modifiers, err: %s", err)
|
||||
}
|
||||
return resModifiers, nil
|
||||
}
|
||||
|
||||
func addQuotes(value string) bool {
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
// if value is null, then don't add quotes
|
||||
if value == "null" {
|
||||
return false
|
||||
}
|
||||
// if value is a boolean, then don't add quotes
|
||||
if _, err := strconv.ParseBool(value); err == nil {
|
||||
return false
|
||||
}
|
||||
// if value is a json object or array, then don't add quotes.
|
||||
if strings.HasPrefix(value, "{") || strings.HasPrefix(value, "[") {
|
||||
return false
|
||||
}
|
||||
// if value is a number, then don't add quotes
|
||||
if _, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
type patcher interface {
|
||||
Patch(u *unstructured.Unstructured, logger logrus.FieldLogger) (*unstructured.Unstructured, error)
|
||||
}
|
||||
|
||||
func (r *ResourceModifierRule) applyPatch(u *unstructured.Unstructured, scheme *runtime.Scheme, logger logrus.FieldLogger) error {
|
||||
var p patcher
|
||||
if len(r.Patches) > 0 {
|
||||
p = &JSONPatcher{patches: r.Patches}
|
||||
} else if len(r.MergePatches) > 0 {
|
||||
p = &JSONMergePatcher{patches: r.MergePatches}
|
||||
} else if len(r.StrategicPatches) > 0 {
|
||||
p = &StrategicMergePatcher{patches: r.StrategicPatches, scheme: scheme}
|
||||
} else {
|
||||
return fmt.Errorf("no patch data found")
|
||||
}
|
||||
|
||||
updated, err := p.Patch(u, logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in applying patch %s", err)
|
||||
}
|
||||
|
||||
u.SetUnstructuredContent(updated.Object)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
@@ -131,6 +135,128 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
cm5 := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-configmap",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: pods\n namespaces:\n - ns1\n matches:\n - path: /metadata/annotations/foo\n value: bar\n mergePatches:\n - patchData: |\n metadata:\n annotations:\n foo: null",
|
||||
},
|
||||
}
|
||||
|
||||
rules5 := &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{
|
||||
"ns1",
|
||||
},
|
||||
Matches: []MatchRule{
|
||||
{
|
||||
Path: "/metadata/annotations/foo",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: "metadata:\n annotations:\n foo: null",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cm6 := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-configmap",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: pods\n namespaces:\n - ns1\n strategicPatches:\n - patchData: |\n spec:\n containers:\n - name: nginx\n image: repo2/nginx",
|
||||
},
|
||||
}
|
||||
|
||||
rules6 := &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{
|
||||
"ns1",
|
||||
},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchData: "spec:\n containers:\n - name: nginx\n image: repo2/nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cm7 := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-configmap",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: pods\n namespaces:\n - ns1\n mergePatches:\n - patchData: |\n {\"metadata\":{\"annotations\":{\"foo\":null}}}",
|
||||
},
|
||||
}
|
||||
|
||||
rules7 := &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{
|
||||
"ns1",
|
||||
},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `{"metadata":{"annotations":{"foo":null}}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cm8 := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-configmap",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: pods\n namespaces:\n - ns1\n strategicPatches:\n - patchData: |\n {\"spec\":{\"containers\":[{\"name\": \"nginx\",\"image\": \"repo2/nginx\"}]}}",
|
||||
},
|
||||
}
|
||||
|
||||
rules8 := &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{
|
||||
"ns1",
|
||||
},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchData: `{"spec":{"containers":[{"name": "nginx","image": "repo2/nginx"}]}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type args struct {
|
||||
cm *v1.ConfigMap
|
||||
}
|
||||
@@ -180,6 +306,38 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "complex yaml data with json merge patch",
|
||||
args: args{
|
||||
cm: cm5,
|
||||
},
|
||||
want: rules5,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "complex yaml data with strategic merge patch",
|
||||
args: args{
|
||||
cm: cm6,
|
||||
},
|
||||
want: rules6,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "complex json data with json merge patch",
|
||||
args: args{
|
||||
cm: cm7,
|
||||
},
|
||||
want: rules7,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "complex json data with strategic merge patch",
|
||||
args: args{
|
||||
cm: cm8,
|
||||
},
|
||||
want: rules8,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -502,6 +660,38 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
wantErr: false,
|
||||
wantObj: deployNginxTwoReplica.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "nginx deployment: Empty Resource Regex",
|
||||
fields: fields{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "deployments.apps",
|
||||
Namespaces: []string{"foo"},
|
||||
},
|
||||
Patches: []JSONPatch{
|
||||
{
|
||||
Operation: "test",
|
||||
Path: "/spec/replicas",
|
||||
Value: "1",
|
||||
},
|
||||
{
|
||||
Operation: "replace",
|
||||
Path: "/spec/replicas",
|
||||
Value: "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
obj: deployNginxOneReplica.DeepCopy(),
|
||||
groupResource: "deployments.apps",
|
||||
},
|
||||
wantErr: false,
|
||||
wantObj: deployNginxTwoReplica.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "nginx deployment: Empty Resource Regex and namespaces list",
|
||||
fields: fields{
|
||||
@@ -719,7 +909,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
Version: tt.fields.Version,
|
||||
ResourceModifierRules: tt.fields.ResourceModifierRules,
|
||||
}
|
||||
got := p.ApplyResourceModifierRules(tt.args.obj, tt.args.groupResource, logrus.New())
|
||||
got := p.ApplyResourceModifierRules(tt.args.obj, tt.args.groupResource, nil, logrus.New())
|
||||
|
||||
assert.Equal(t, tt.wantErr, len(got) > 0)
|
||||
assert.Equal(t, *tt.wantObj, *tt.args.obj)
|
||||
@@ -727,6 +917,633 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var podYAMLWithNginxImage = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
namespace: fake
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
`
|
||||
|
||||
var podYAMLWithNginx1Image = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
namespace: fake
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx1
|
||||
name: nginx
|
||||
`
|
||||
|
||||
var podYAMLWithNFSVolume = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
namespace: fake
|
||||
spec:
|
||||
containers:
|
||||
- image: fake
|
||||
name: fake
|
||||
volumeMounts:
|
||||
- mountPath: /fake1
|
||||
name: vol1
|
||||
- mountPath: /fake2
|
||||
name: vol2
|
||||
volumes:
|
||||
- name: vol1
|
||||
nfs:
|
||||
path: /fake2
|
||||
- name: vol2
|
||||
emptyDir: {}
|
||||
`
|
||||
|
||||
var podYAMLWithPVCVolume = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
namespace: fake
|
||||
spec:
|
||||
containers:
|
||||
- image: fake
|
||||
name: fake
|
||||
volumeMounts:
|
||||
- mountPath: /fake1
|
||||
name: vol1
|
||||
- mountPath: /fake2
|
||||
name: vol2
|
||||
volumes:
|
||||
- name: vol1
|
||||
persistentVolumeClaim:
|
||||
claimName: pvc1
|
||||
- name: vol2
|
||||
emptyDir: {}
|
||||
`
|
||||
|
||||
var svcYAMLWithPort8000 = `
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: svc1
|
||||
namespace: fake
|
||||
spec:
|
||||
ports:
|
||||
- name: fake1
|
||||
port: 8001
|
||||
protocol: TCP
|
||||
targetPort: 8001
|
||||
- name: fake
|
||||
port: 8000
|
||||
protocol: TCP
|
||||
targetPort: 8000
|
||||
- name: fake2
|
||||
port: 8002
|
||||
protocol: TCP
|
||||
targetPort: 8002
|
||||
`
|
||||
|
||||
var svcYAMLWithPort9000 = `
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: svc1
|
||||
namespace: fake
|
||||
spec:
|
||||
ports:
|
||||
- name: fake1
|
||||
port: 8001
|
||||
protocol: TCP
|
||||
targetPort: 8001
|
||||
- name: fake
|
||||
port: 9000
|
||||
protocol: TCP
|
||||
targetPort: 9000
|
||||
- name: fake2
|
||||
port: 8002
|
||||
protocol: TCP
|
||||
targetPort: 8002
|
||||
`
|
||||
|
||||
var cmYAMLWithLabelAToB = `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm1
|
||||
namespace: fake
|
||||
labels:
|
||||
a: b
|
||||
c: d
|
||||
`
|
||||
|
||||
var cmYAMLWithLabelAToC = `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm1
|
||||
namespace: fake
|
||||
labels:
|
||||
a: c
|
||||
c: d
|
||||
`
|
||||
|
||||
var cmYAMLWithoutLabelA = `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm1
|
||||
namespace: fake
|
||||
labels:
|
||||
c: d
|
||||
`
|
||||
|
||||
func TestResourceModifiers_ApplyResourceModifierRules_StrategicMergePatch(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
unstructuredSerializer := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
o1, _, err := unstructuredSerializer.Decode([]byte(podYAMLWithNFSVolume), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
podWithNFSVolume := o1.(*unstructured.Unstructured)
|
||||
|
||||
o2, _, err := unstructuredSerializer.Decode([]byte(podYAMLWithPVCVolume), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
podWithPVCVolume := o2.(*unstructured.Unstructured)
|
||||
|
||||
o3, _, err := unstructuredSerializer.Decode([]byte(svcYAMLWithPort8000), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
svcWithPort8000 := o3.(*unstructured.Unstructured)
|
||||
|
||||
o4, _, err := unstructuredSerializer.Decode([]byte(svcYAMLWithPort9000), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
svcWithPort9000 := o4.(*unstructured.Unstructured)
|
||||
|
||||
o5, _, err := unstructuredSerializer.Decode([]byte(podYAMLWithNginxImage), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
podWithNginxImage := o5.(*unstructured.Unstructured)
|
||||
|
||||
o6, _, err := unstructuredSerializer.Decode([]byte(podYAMLWithNginx1Image), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
podWithNginx1Image := o6.(*unstructured.Unstructured)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rm *ResourceModifiers
|
||||
obj *unstructured.Unstructured
|
||||
groupResource string
|
||||
wantErr bool
|
||||
wantObj *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "update image",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchData: `{"spec":{"containers":[{"name":"nginx","image":"nginx1"}]}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: podWithNginxImage.DeepCopy(),
|
||||
groupResource: "pods",
|
||||
wantErr: false,
|
||||
wantObj: podWithNginx1Image.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "update image with yaml format",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchData: `spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx1`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: podWithNginxImage.DeepCopy(),
|
||||
groupResource: "pods",
|
||||
wantErr: false,
|
||||
wantObj: podWithNginx1Image.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "replace nfs with pvc in volume",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchData: `{"spec":{"volumes":[{"nfs":null,"name":"vol1","persistentVolumeClaim":{"claimName":"pvc1"}}]}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: podWithNFSVolume.DeepCopy(),
|
||||
groupResource: "pods",
|
||||
wantErr: false,
|
||||
wantObj: podWithPVCVolume.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "replace any other volume source with pvc in volume",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchData: `{"spec":{"volumes":[{"$retainKeys":["name","persistentVolumeClaim"],"name":"vol1","persistentVolumeClaim":{"claimName":"pvc1"}}]}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: podWithNFSVolume.DeepCopy(),
|
||||
groupResource: "pods",
|
||||
wantErr: false,
|
||||
wantObj: podWithPVCVolume.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "update a service port",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "services",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchData: `{"spec":{"$setElementOrder/ports":[{"port":8001},{"port":9000},{"port":8002}],"ports":[{"name":"fake","port":9000,"protocol":"TCP","targetPort":9000},{"$patch":"delete","port":8000}]}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: svcWithPort8000.DeepCopy(),
|
||||
groupResource: "services",
|
||||
wantErr: false,
|
||||
wantObj: svcWithPort9000.DeepCopy(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.rm.ApplyResourceModifierRules(tt.obj, tt.groupResource, scheme, logrus.New())
|
||||
|
||||
assert.Equal(t, tt.wantErr, len(got) > 0)
|
||||
assert.Equal(t, *tt.wantObj, *tt.obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceModifiers_ApplyResourceModifierRules_JSONMergePatch(t *testing.T) {
|
||||
unstructuredSerializer := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
o1, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToB), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToB := o1.(*unstructured.Unstructured)
|
||||
|
||||
o2, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToC), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToC := o2.(*unstructured.Unstructured)
|
||||
|
||||
o3, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithoutLabelA), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithoutLabelA := o3.(*unstructured.Unstructured)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rm *ResourceModifiers
|
||||
obj *unstructured.Unstructured
|
||||
groupResource string
|
||||
wantErr bool
|
||||
wantObj *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "update labels",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "configmaps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `{"metadata":{"labels":{"a":"c"}}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToC.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "update labels in yaml format",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "configmaps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `metadata:
|
||||
labels:
|
||||
a: c`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToC.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "delete labels",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "configmaps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `{"metadata":{"labels":{"a":null}}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithoutLabelA.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "add labels",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "configmaps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `{"metadata":{"labels":{"a":"b"}}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithoutLabelA.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToB.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "delete non-existing labels",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "configmaps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `{"metadata":{"labels":{"a":null}}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithoutLabelA.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithoutLabelA.DeepCopy(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.rm.ApplyResourceModifierRules(tt.obj, tt.groupResource, nil, logrus.New())
|
||||
|
||||
assert.Equal(t, tt.wantErr, len(got) > 0)
|
||||
assert.Equal(t, *tt.wantObj, *tt.obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceModifiers_wildcard_in_GroupResource(t *testing.T) {
|
||||
unstructuredSerializer := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
o1, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToB), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToB := o1.(*unstructured.Unstructured)
|
||||
|
||||
o2, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToC), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToC := o2.(*unstructured.Unstructured)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rm *ResourceModifiers
|
||||
obj *unstructured.Unstructured
|
||||
groupResource string
|
||||
wantErr bool
|
||||
wantObj *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "match all groups and resources",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "*",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `{"metadata":{"labels":{"a":"c"}}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToC.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "match all resources in group apps",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "*.apps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `{"metadata":{"labels":{"a":"c"}}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "fake.apps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToC.DeepCopy(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.rm.ApplyResourceModifierRules(tt.obj, tt.groupResource, nil, logrus.New())
|
||||
|
||||
assert.Equal(t, tt.wantErr, len(got) > 0)
|
||||
assert.Equal(t, *tt.wantObj, *tt.obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceModifiers_conditional_patches(t *testing.T) {
|
||||
unstructuredSerializer := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
o1, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToB), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToB := o1.(*unstructured.Unstructured)
|
||||
|
||||
o2, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToC), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToC := o2.(*unstructured.Unstructured)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rm *ResourceModifiers
|
||||
obj *unstructured.Unstructured
|
||||
groupResource string
|
||||
wantErr bool
|
||||
wantObj *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "match conditions and apply patches",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "*",
|
||||
Namespaces: []string{"fake"},
|
||||
Matches: []MatchRule{
|
||||
{
|
||||
Path: "/metadata/labels/a",
|
||||
Value: "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `{"metadata":{"labels":{"a":"c"}}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToC.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "mismatch conditions and skip patches",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "*",
|
||||
Namespaces: []string{"fake"},
|
||||
Matches: []MatchRule{
|
||||
{
|
||||
Path: "/metadata/labels/a",
|
||||
Value: "c",
|
||||
},
|
||||
},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `{"metadata":{"labels":{"a":"c"}}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToB.DeepCopy(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.rm.ApplyResourceModifierRules(tt.obj, tt.groupResource, nil, logrus.New())
|
||||
|
||||
assert.Equal(t, tt.wantErr, len(got) > 0)
|
||||
assert.Equal(t, *tt.wantObj, *tt.obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONPatch_ToString(t *testing.T) {
|
||||
type fields struct {
|
||||
Operation string
|
||||
|
||||
@@ -24,6 +24,21 @@ func (r *ResourceModifierRule) Validate() error {
|
||||
if err := r.Conditions.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count := 0
|
||||
for _, size := range []int{
|
||||
len(r.Patches),
|
||||
len(r.MergePatches),
|
||||
len(r.StrategicPatches),
|
||||
} {
|
||||
if size != 0 {
|
||||
count++
|
||||
}
|
||||
if count >= 2 {
|
||||
return fmt.Errorf("only one of patches, mergePatches, strategicPatches can be specified")
|
||||
}
|
||||
}
|
||||
|
||||
for _, patch := range r.Patches {
|
||||
if err := patch.Validate(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -129,6 +129,32 @@ func TestResourceModifiers_Validate(t *testing.T) {
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "More than one patch type in a rule",
|
||||
fields: fields{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "*",
|
||||
},
|
||||
Patches: []JSONPatch{
|
||||
{
|
||||
Operation: "test",
|
||||
Path: "/spec/storageClassName",
|
||||
Value: "premium",
|
||||
},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchData: `{"metadata":{"labels":{"a":null}}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package resourcemodifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
kubejson "sigs.k8s.io/json"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type StrategicMergePatch struct {
|
||||
PatchData string `json:"patchData,omitempty"`
|
||||
}
|
||||
|
||||
type StrategicMergePatcher struct {
|
||||
patches []StrategicMergePatch
|
||||
scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
func (p *StrategicMergePatcher) Patch(u *unstructured.Unstructured, _ logrus.FieldLogger) (*unstructured.Unstructured, error) {
|
||||
gvk := u.GetObjectKind().GroupVersionKind()
|
||||
schemaReferenceObj, err := p.scheme.New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
origin := u.DeepCopy()
|
||||
updated := u.DeepCopy()
|
||||
for _, patch := range p.patches {
|
||||
patchBytes, err := yaml.YAMLToJSON([]byte(patch.PatchData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in converting YAML to JSON %s", err)
|
||||
}
|
||||
|
||||
err = strategicPatchObject(origin, patchBytes, updated, schemaReferenceObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in applying Strategic Patch %s", err.Error())
|
||||
}
|
||||
|
||||
origin = updated.DeepCopy()
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// strategicPatchObject applies a strategic merge patch of `patchBytes` to
|
||||
// `originalObject` and stores the result in `objToUpdate`.
|
||||
// It additionally returns the map[string]interface{} representation of the
|
||||
// `originalObject` and `patchBytes`.
|
||||
// NOTE: Both `originalObject` and `objToUpdate` are supposed to be versioned.
|
||||
func strategicPatchObject(
|
||||
originalObject runtime.Object,
|
||||
patchBytes []byte,
|
||||
objToUpdate runtime.Object,
|
||||
schemaReferenceObj runtime.Object,
|
||||
) error {
|
||||
originalObjMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(originalObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
patchMap := make(map[string]interface{})
|
||||
var strictErrs []error
|
||||
strictErrs, err = kubejson.UnmarshalStrict(patchBytes, &patchMap)
|
||||
if err != nil {
|
||||
return apierrors.NewBadRequest(err.Error())
|
||||
}
|
||||
|
||||
if err := applyPatchToObject(originalObjMap, patchMap, objToUpdate, schemaReferenceObj, strictErrs); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyPatchToObject applies a strategic merge patch of <patchMap> to
|
||||
// <originalMap> and stores the result in <objToUpdate>.
|
||||
// NOTE: <objToUpdate> must be a versioned object.
|
||||
func applyPatchToObject(
|
||||
originalMap map[string]interface{},
|
||||
patchMap map[string]interface{},
|
||||
objToUpdate runtime.Object,
|
||||
schemaReferenceObj runtime.Object,
|
||||
strictErrs []error,
|
||||
) error {
|
||||
patchedObjMap, err := strategicpatch.StrategicMergeMapPatch(originalMap, patchMap, schemaReferenceObj)
|
||||
if err != nil {
|
||||
return interpretStrategicMergePatchError(err)
|
||||
}
|
||||
|
||||
// Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object
|
||||
converter := runtime.DefaultUnstructuredConverter
|
||||
if err := converter.FromUnstructuredWithValidation(patchedObjMap, objToUpdate, true); err != nil {
|
||||
strictError, isStrictError := runtime.AsStrictDecodingError(err)
|
||||
switch {
|
||||
case !isStrictError:
|
||||
// disregard any sttrictErrs, because it's an incomplete
|
||||
// list of strict errors given that we don't know what fields were
|
||||
// unknown because StrategicMergeMapPatch failed.
|
||||
// Non-strict errors trump in this case.
|
||||
return apierrors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{
|
||||
field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), err.Error()),
|
||||
})
|
||||
//case validationDirective == metav1.FieldValidationWarn:
|
||||
// addStrictDecodingWarnings(requestContext, append(strictErrs, strictError.Errors()...))
|
||||
default:
|
||||
strictDecodingError := runtime.NewStrictDecodingError(append(strictErrs, strictError.Errors()...))
|
||||
return apierrors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{
|
||||
field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), strictDecodingError.Error()),
|
||||
})
|
||||
}
|
||||
} else if len(strictErrs) > 0 {
|
||||
switch {
|
||||
//case validationDirective == metav1.FieldValidationWarn:
|
||||
// addStrictDecodingWarnings(requestContext, strictErrs)
|
||||
default:
|
||||
return apierrors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{
|
||||
field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), runtime.NewStrictDecodingError(strictErrs).Error()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// interpretStrategicMergePatchError interprets the error type and returns an error with appropriate HTTP code.
|
||||
func interpretStrategicMergePatchError(err error) error {
|
||||
switch err {
|
||||
case mergepatch.ErrBadJSONDoc, mergepatch.ErrBadPatchFormatForPrimitiveList, mergepatch.ErrBadPatchFormatForRetainKeys, mergepatch.ErrBadPatchFormatForSetElementOrderList, mergepatch.ErrUnsupportedStrategicMergePatchFormat:
|
||||
return apierrors.NewBadRequest(err.Error())
|
||||
case mergepatch.ErrNoListOfLists, mergepatch.ErrPatchContentNotMatchRetainKeys:
|
||||
return apierrors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package resourcemodifiers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func TestStrategicMergePatchFailure(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data string
|
||||
kind string
|
||||
}{
|
||||
{
|
||||
name: "patch with unknown kind",
|
||||
data: "{}",
|
||||
kind: "BadKind",
|
||||
},
|
||||
{
|
||||
name: "patch with bad yaml",
|
||||
data: "a: b:",
|
||||
kind: "Pod",
|
||||
},
|
||||
{
|
||||
name: "patch with bad json",
|
||||
data: `{"a"::1}`,
|
||||
kind: "Pod",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
err := clientgoscheme.AddToScheme(scheme)
|
||||
assert.NoError(t, err)
|
||||
pt := &StrategicMergePatcher{
|
||||
patches: []StrategicMergePatch{{PatchData: tt.data}},
|
||||
scheme: scheme,
|
||||
}
|
||||
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: tt.kind})
|
||||
_, err = pt.Patch(u, logrus.New())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@ import (
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
"github.com/vmware-tanzu/velero/pkg/util"
|
||||
)
|
||||
|
||||
func TestIsReadyToValidate(t *testing.T) {
|
||||
@@ -163,7 +163,7 @@ func TestListBackupStorageLocations(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(tt.backupLocations).Build()
|
||||
client := fake.NewClientBuilder().WithScheme(util.VeleroScheme).WithRuntimeObjects(tt.backupLocations).Build()
|
||||
if tt.expectError {
|
||||
_, err := ListBackupStorageLocations(context.Background(), client, "ns-1")
|
||||
g.Expect(err).NotTo(BeNil())
|
||||
|
||||
+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"
|
||||
|
||||
@@ -268,12 +268,12 @@ type ExecHook struct {
|
||||
type HookErrorMode string
|
||||
|
||||
const (
|
||||
// HookErrorModeContinue means that an error from a hook is acceptable, and the backup can
|
||||
// proceed.
|
||||
// HookErrorModeContinue means that an error from a hook is acceptable and the backup/restore can
|
||||
// proceed with the rest of hooks' execution. This backup/restore should be in `PartiallyFailed` status.
|
||||
HookErrorModeContinue HookErrorMode = "Continue"
|
||||
|
||||
// HookErrorModeFail means that an error from a hook is problematic, and the backup should be in
|
||||
// error.
|
||||
// HookErrorModeFail means that an error from a hook is problematic and Velero should stop executing following hooks.
|
||||
// This backup/restore should be in `PartiallyFailed` status.
|
||||
HookErrorModeFail HookErrorMode = "Fail"
|
||||
)
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ type DataDownloadStatus struct {
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time duration since this DataDownload was created"
|
||||
// +kubebuilder:printcolumn:name="Node",type="string",JSONPath=".status.node",description="Name of the node where the DataDownload is processed"
|
||||
|
||||
// DataDownload acts as the protocol between data mover plugins and data mover controller for the datamover restore operation
|
||||
type DataDownload struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ type DataUploadStatus struct {
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time duration since this DataUpload was created"
|
||||
// +kubebuilder:printcolumn:name="Node",type="string",JSONPath=".status.node",description="Name of the node where the DataUpload is processed"
|
||||
|
||||
// DataUpload acts as the protocol between data mover plugins and data mover controller for the datamover backup operation
|
||||
type DataUpload struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/hook"
|
||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
@@ -51,7 +49,6 @@ type Request struct {
|
||||
VolumeSnapshots []*volume.Snapshot
|
||||
PodVolumeBackups []*velerov1api.PodVolumeBackup
|
||||
BackedUpItems map[itemKey]struct{}
|
||||
CSISnapshots []snapshotv1api.VolumeSnapshot
|
||||
itemOperationsList *[]*itemoperation.BackupOperation
|
||||
ResPolicies *resourcepolicies.Policies
|
||||
SkippedPVTracker *skipPVTracker
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v4/listers/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/features"
|
||||
"github.com/vmware-tanzu/velero/pkg/label"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
)
|
||||
|
||||
// Common function to update the status of CSI snapshots
|
||||
// returns VolumeSnapshot, VolumeSnapshotContent, VolumeSnapshotClasses referenced
|
||||
func UpdateBackupCSISnapshotsStatus(client kbclient.Client, volumeSnapshotLister snapshotv1listers.VolumeSnapshotLister, backup *velerov1api.Backup, backupLog logrus.FieldLogger) (volumeSnapshots []snapshotv1api.VolumeSnapshot, volumeSnapshotContents []snapshotv1api.VolumeSnapshotContent, volumeSnapshotClasses []snapshotv1api.VolumeSnapshotClass) {
|
||||
if boolptr.IsSetToTrue(backup.Spec.SnapshotMoveData) {
|
||||
backupLog.Info("backup SnapshotMoveData is set to true, skip VolumeSnapshot resource persistence.")
|
||||
} else if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
||||
selector := label.NewSelectorForBackup(backup.Name)
|
||||
vscList := &snapshotv1api.VolumeSnapshotContentList{}
|
||||
|
||||
if volumeSnapshotLister != nil {
|
||||
tmpVSs, err := volumeSnapshotLister.List(label.NewSelectorForBackup(backup.Name))
|
||||
if err != nil {
|
||||
backupLog.Error(err)
|
||||
}
|
||||
for _, vs := range tmpVSs {
|
||||
volumeSnapshots = append(volumeSnapshots, *vs)
|
||||
}
|
||||
}
|
||||
|
||||
err := client.List(context.Background(), vscList, &kbclient.ListOptions{LabelSelector: selector})
|
||||
if err != nil {
|
||||
backupLog.Error(err)
|
||||
}
|
||||
if len(vscList.Items) >= 0 {
|
||||
volumeSnapshotContents = vscList.Items
|
||||
}
|
||||
|
||||
vsClassSet := sets.NewString()
|
||||
for index := range volumeSnapshotContents {
|
||||
// persist the volumesnapshotclasses referenced by vsc
|
||||
if volumeSnapshotContents[index].Spec.VolumeSnapshotClassName != nil && !vsClassSet.Has(*volumeSnapshotContents[index].Spec.VolumeSnapshotClassName) {
|
||||
vsClass := &snapshotv1api.VolumeSnapshotClass{}
|
||||
if err := client.Get(context.TODO(), kbclient.ObjectKey{Name: *volumeSnapshotContents[index].Spec.VolumeSnapshotClassName}, vsClass); err != nil {
|
||||
backupLog.Error(err)
|
||||
} else {
|
||||
vsClassSet.Insert(*volumeSnapshotContents[index].Spec.VolumeSnapshotClassName)
|
||||
volumeSnapshotClasses = append(volumeSnapshotClasses, *vsClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
backup.Status.CSIVolumeSnapshotsAttempted = len(volumeSnapshots)
|
||||
csiVolumeSnapshotsCompleted := 0
|
||||
for _, vs := range volumeSnapshots {
|
||||
if vs.Status != nil && boolptr.IsSetToTrue(vs.Status.ReadyToUse) {
|
||||
csiVolumeSnapshotsCompleted++
|
||||
}
|
||||
}
|
||||
backup.Status.CSIVolumeSnapshotsCompleted = csiVolumeSnapshotsCompleted
|
||||
}
|
||||
return volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses
|
||||
}
|
||||
@@ -305,3 +305,9 @@ func (b *BackupBuilder) ParallelFilesUpload(parallel int) *BackupBuilder {
|
||||
b.object.Spec.UploaderConfig.ParallelFilesUpload = parallel
|
||||
return b
|
||||
}
|
||||
|
||||
// WithStatus sets the Backup's status.
|
||||
func (b *BackupBuilder) WithStatus(status velerov1api.BackupStatus) *BackupBuilder {
|
||||
b.object.Status = status
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -41,6 +41,11 @@ func ForNode(name string) *NodeBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NodeBuilder) Labels(labels map[string]string) *NodeBuilder {
|
||||
b.object.Labels = labels
|
||||
return b
|
||||
}
|
||||
|
||||
// Result returns the built Node.
|
||||
func (b *NodeBuilder) Result() *corev1api.Node {
|
||||
return b.object
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -101,3 +101,8 @@ func (b *PodBuilder) ContainerStatuses(containerStatuses ...*corev1api.Container
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *PodBuilder) Phase(phase corev1api.PodPhase) *PodBuilder {
|
||||
b.object.Status.Phase = phase
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -67,3 +67,8 @@ func (v *VolumeSnapshotBuilder) BoundVolumeSnapshotContentName(vscName string) *
|
||||
v.object.Status.BoundVolumeSnapshotContentName = &vscName
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *VolumeSnapshotBuilder) SourcePVC(name string) *VolumeSnapshotBuilder {
|
||||
v.object.Spec.Source.PersistentVolumeClaimName = &name
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import (
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
|
||||
)
|
||||
|
||||
//go:generate mockery --name Factory
|
||||
@@ -42,9 +41,6 @@ import (
|
||||
type Factory interface {
|
||||
// BindFlags binds common flags (--kubeconfig, --namespace) to the passed-in FlagSet.
|
||||
BindFlags(flags *pflag.FlagSet)
|
||||
// Client returns a VeleroClient. It uses the following priority to specify the cluster
|
||||
// configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration.
|
||||
Client() (clientset.Interface, error)
|
||||
// KubeClient returns a Kubernetes client. It uses the following priority to specify the cluster
|
||||
// configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration.
|
||||
KubeClient() (kubernetes.Interface, error)
|
||||
@@ -115,19 +111,6 @@ func (f *factory) ClientConfig() (*rest.Config, error) {
|
||||
return Config(f.kubeconfig, f.kubecontext, f.baseName, f.clientQPS, f.clientBurst)
|
||||
}
|
||||
|
||||
func (f *factory) Client() (clientset.Interface, error) {
|
||||
clientConfig, err := f.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
veleroClient, err := clientset.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return veleroClient, nil
|
||||
}
|
||||
|
||||
func (f *factory) KubeClient() (kubernetes.Interface, error) {
|
||||
clientConfig, err := f.ClientConfig()
|
||||
if err != nil {
|
||||
|
||||
@@ -112,11 +112,6 @@ func TestFactory(t *testing.T) {
|
||||
assert.Equal(t, test.burst, clientConfig.Burst)
|
||||
strings.Contains(clientConfig.UserAgent, test.baseName)
|
||||
|
||||
client, _ := f.Client()
|
||||
_, e := client.Discovery().ServerGroups()
|
||||
assert.Contains(t, e.Error(), fmt.Sprintf("Get \"%s/api?timeout=", test.expectedHost))
|
||||
assert.NotNil(t, client)
|
||||
|
||||
kubeClient, _ := f.KubeClient()
|
||||
group := kubeClient.NodeV1().RESTClient().APIVersion().Group
|
||||
assert.NotNil(t, kubeClient)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.30.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.20.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -13,8 +13,6 @@ import (
|
||||
pkgclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
rest "k8s.io/client-go/rest"
|
||||
|
||||
versioned "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
|
||||
)
|
||||
|
||||
// Factory is an autogenerated mock type for the Factory type
|
||||
@@ -27,32 +25,6 @@ func (_m *Factory) BindFlags(flags *pflag.FlagSet) {
|
||||
_m.Called(flags)
|
||||
}
|
||||
|
||||
// Client provides a mock function with given fields:
|
||||
func (_m *Factory) Client() (versioned.Interface, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 versioned.Interface
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (versioned.Interface, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() versioned.Interface); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(versioned.Interface)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ClientConfig provides a mock function with given fields:
|
||||
func (_m *Factory) ClientConfig() (*rest.Config, error) {
|
||||
ret := _m.Called()
|
||||
@@ -212,12 +184,13 @@ func (_m *Factory) SetClientQPS(_a0 float32) {
|
||||
_m.Called(_a0)
|
||||
}
|
||||
|
||||
// NewFactory creates a new instance of Factory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewFactory(t interface {
|
||||
type mockConstructorTestingTNewFactory interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Factory {
|
||||
}
|
||||
|
||||
// NewFactory creates a new instance of Factory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewFactory(t mockConstructorTestingTNewFactory) *Factory {
|
||||
mock := &Factory{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
|
||||
+2
-8
@@ -25,20 +25,14 @@ import (
|
||||
)
|
||||
|
||||
func CreateRetryGenerateName(client kbclient.Client, ctx context.Context, obj kbclient.Object) error {
|
||||
return CreateRetryGenerateNameWithFunc(obj, func() error {
|
||||
return client.Create(ctx, obj, &kbclient.CreateOptions{})
|
||||
})
|
||||
}
|
||||
|
||||
func CreateRetryGenerateNameWithFunc(obj kbclient.Object, createFn func() error) error {
|
||||
retryCreateFn := func() error {
|
||||
// needed to ensure that the name from the failed create isn't left on the object between retries
|
||||
obj.SetName("")
|
||||
return createFn()
|
||||
return client.Create(ctx, obj, &kbclient.CreateOptions{})
|
||||
}
|
||||
if obj.GetGenerateName() != "" && obj.GetName() == "" {
|
||||
return retry.OnError(retry.DefaultRetry, apierrors.IsAlreadyExists, retryCreateFn)
|
||||
} else {
|
||||
return createFn()
|
||||
return client.Create(ctx, obj, &kbclient.CreateOptions{})
|
||||
}
|
||||
}
|
||||
|
||||
+16
-11
@@ -30,9 +30,12 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/vmware-tanzu/crash-diagnostics/exec"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd"
|
||||
)
|
||||
@@ -110,30 +113,32 @@ func (o *option) complete(f client.Factory, fs *pflag.FlagSet) error {
|
||||
}
|
||||
|
||||
func (o *option) validate(f client.Factory) error {
|
||||
kubeClient, err := f.KubeClient()
|
||||
crClient, err := f.KubebuilderClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l, err := kubeClient.AppsV1().Deployments(o.namespace).List(context.TODO(), metav1.ListOptions{
|
||||
LabelSelector: "component=velero",
|
||||
deploymentList := new(appsv1.DeploymentList)
|
||||
selector, err := labels.Parse("component=velero")
|
||||
cmd.CheckError(err)
|
||||
err = crClient.List(context.TODO(), deploymentList, &ctrlclient.ListOptions{
|
||||
Namespace: o.namespace,
|
||||
LabelSelector: selector,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to check velero deployment")
|
||||
}
|
||||
if len(l.Items) == 0 {
|
||||
if len(deploymentList.Items) == 0 {
|
||||
return fmt.Errorf("velero deployment does not exist in namespace: %s", o.namespace)
|
||||
}
|
||||
veleroClient, err := f.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(o.backup) > 0 {
|
||||
if _, err := veleroClient.VeleroV1().Backups(o.namespace).Get(context.TODO(), o.backup, metav1.GetOptions{}); err != nil {
|
||||
backup := new(velerov1api.Backup)
|
||||
if err := crClient.Get(context.TODO(), ctrlclient.ObjectKey{Namespace: o.namespace, Name: o.backup}, backup); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(o.restore) > 0 {
|
||||
if _, err := veleroClient.VeleroV1().Restores(o.namespace).Get(context.TODO(), o.restore, metav1.GetOptions{}); err != nil {
|
||||
restore := new(velerov1api.Restore)
|
||||
if err := crClient.Get(context.TODO(), ctrlclient.ObjectKey{Namespace: o.namespace, Name: o.restore}, restore); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package nodeagent
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -32,6 +33,7 @@ import (
|
||||
storagev1api "k8s.io/api/storage/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
@@ -53,7 +55,9 @@ import (
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd/util/signals"
|
||||
"github.com/vmware-tanzu/velero/pkg/controller"
|
||||
"github.com/vmware-tanzu/velero/pkg/datapath"
|
||||
"github.com/vmware-tanzu/velero/pkg/metrics"
|
||||
"github.com/vmware-tanzu/velero/pkg/nodeagent"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/logging"
|
||||
@@ -73,6 +77,7 @@ const (
|
||||
|
||||
defaultResourceTimeout = 10 * time.Minute
|
||||
defaultDataMoverPrepareTimeout = 30 * time.Minute
|
||||
defaultDataPathConcurrentNum = 1
|
||||
)
|
||||
|
||||
type nodeAgentServerConfig struct {
|
||||
@@ -132,6 +137,7 @@ type nodeAgentServer struct {
|
||||
config nodeAgentServerConfig
|
||||
kubeClient kubernetes.Interface
|
||||
csiSnapshotClient *snapshotv1client.Clientset
|
||||
dataPathMgr *datapath.Manager
|
||||
}
|
||||
|
||||
func newNodeAgentServer(logger logrus.FieldLogger, factory client.Factory, config nodeAgentServerConfig) (*nodeAgentServer, error) {
|
||||
@@ -219,6 +225,10 @@ func newNodeAgentServer(logger logrus.FieldLogger, factory client.Factory, confi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataPathConcurrentNum := s.getDataPathConcurrentNum(defaultDataPathConcurrentNum)
|
||||
s.dataPathMgr = datapath.NewManager(dataPathConcurrentNum)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@@ -263,24 +273,24 @@ func (s *nodeAgentServer) run() {
|
||||
|
||||
credentialGetter := &credentials.CredentialGetter{FromFile: credentialFileStore, FromSecret: credSecretStore}
|
||||
repoEnsurer := repository.NewEnsurer(s.mgr.GetClient(), s.logger, s.config.resourceTimeout)
|
||||
pvbReconciler := controller.NewPodVolumeBackupReconciler(s.mgr.GetClient(), repoEnsurer,
|
||||
pvbReconciler := controller.NewPodVolumeBackupReconciler(s.mgr.GetClient(), s.dataPathMgr, repoEnsurer,
|
||||
credentialGetter, s.nodeName, s.mgr.GetScheme(), s.metrics, s.logger)
|
||||
|
||||
if err := pvbReconciler.SetupWithManager(s.mgr); err != nil {
|
||||
s.logger.Fatal(err, "unable to create controller", "controller", controller.PodVolumeBackup)
|
||||
}
|
||||
|
||||
if err = controller.NewPodVolumeRestoreReconciler(s.mgr.GetClient(), repoEnsurer, credentialGetter, s.logger).SetupWithManager(s.mgr); err != nil {
|
||||
if err = controller.NewPodVolumeRestoreReconciler(s.mgr.GetClient(), s.dataPathMgr, repoEnsurer, credentialGetter, s.logger).SetupWithManager(s.mgr); err != nil {
|
||||
s.logger.WithError(err).Fatal("Unable to create the pod volume restore controller")
|
||||
}
|
||||
|
||||
dataUploadReconciler := controller.NewDataUploadReconciler(s.mgr.GetClient(), s.kubeClient, s.csiSnapshotClient.SnapshotV1(), repoEnsurer, clock.RealClock{}, credentialGetter, s.nodeName, s.fileSystem, s.config.dataMoverPrepareTimeout, s.logger, s.metrics)
|
||||
dataUploadReconciler := controller.NewDataUploadReconciler(s.mgr.GetClient(), s.kubeClient, s.csiSnapshotClient.SnapshotV1(), s.dataPathMgr, repoEnsurer, clock.RealClock{}, credentialGetter, s.nodeName, s.fileSystem, s.config.dataMoverPrepareTimeout, s.logger, s.metrics)
|
||||
s.markDataUploadsCancel(dataUploadReconciler)
|
||||
if err = dataUploadReconciler.SetupWithManager(s.mgr); err != nil {
|
||||
s.logger.WithError(err).Fatal("Unable to create the data upload controller")
|
||||
}
|
||||
|
||||
dataDownloadReconciler := controller.NewDataDownloadReconciler(s.mgr.GetClient(), s.kubeClient, repoEnsurer, credentialGetter, s.nodeName, s.config.dataMoverPrepareTimeout, s.logger, s.metrics)
|
||||
dataDownloadReconciler := controller.NewDataDownloadReconciler(s.mgr.GetClient(), s.kubeClient, s.dataPathMgr, repoEnsurer, credentialGetter, s.nodeName, s.config.dataMoverPrepareTimeout, s.logger, s.metrics)
|
||||
s.markDataDownloadsCancel(dataDownloadReconciler)
|
||||
if err = dataDownloadReconciler.SetupWithManager(s.mgr); err != nil {
|
||||
s.logger.WithError(err).Fatal("Unable to create the data download controller")
|
||||
@@ -478,3 +488,65 @@ func (s *nodeAgentServer) markInProgressPVRsFailed(client ctrlclient.Client) {
|
||||
s.logger.WithField("podvolumerestore", pvr.GetName()).Warn(pvr.Status.Message)
|
||||
}
|
||||
}
|
||||
|
||||
var getConfigsFunc = nodeagent.GetConfigs
|
||||
|
||||
func (s *nodeAgentServer) getDataPathConcurrentNum(defaultNum int) int {
|
||||
configs, err := getConfigsFunc(s.ctx, s.namespace, s.kubeClient)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Warn("Failed to get node agent configs")
|
||||
return defaultNum
|
||||
}
|
||||
|
||||
if configs == nil || configs.DataPathConcurrency == nil {
|
||||
s.logger.Infof("Concurrency configs are not found, use the default number %v", defaultNum)
|
||||
return defaultNum
|
||||
}
|
||||
|
||||
globalNum := configs.DataPathConcurrency.GlobalConfig
|
||||
|
||||
if globalNum <= 0 {
|
||||
s.logger.Warnf("Global number %v is invalid, use the default value %v", globalNum, defaultNum)
|
||||
globalNum = defaultNum
|
||||
}
|
||||
|
||||
if len(configs.DataPathConcurrency.PerNodeConfig) == 0 {
|
||||
return globalNum
|
||||
}
|
||||
|
||||
curNode, err := s.kubeClient.CoreV1().Nodes().Get(s.ctx, s.nodeName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Warnf("Failed to get node info for %s, use the global number %v", s.nodeName, globalNum)
|
||||
return globalNum
|
||||
}
|
||||
|
||||
concurrentNum := math.MaxInt32
|
||||
|
||||
for _, rule := range configs.DataPathConcurrency.PerNodeConfig {
|
||||
selector, err := metav1.LabelSelectorAsSelector(&rule.NodeSelector)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Warnf("Failed to parse rule with label selector %s, skip it", rule.NodeSelector.String())
|
||||
continue
|
||||
}
|
||||
|
||||
if rule.Number <= 0 {
|
||||
s.logger.Warnf("Rule with label selector %s is with an invalid number %v, skip it", rule.NodeSelector.String(), rule.Number)
|
||||
continue
|
||||
}
|
||||
|
||||
if selector.Matches(labels.Set(curNode.GetLabels())) {
|
||||
if concurrentNum > rule.Number {
|
||||
concurrentNum = rule.Number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if concurrentNum == math.MaxInt32 {
|
||||
s.logger.Infof("Per node number for node %s is not found, use the global number %v", s.nodeName, globalNum)
|
||||
concurrentNum = globalNum
|
||||
} else {
|
||||
s.logger.Infof("Use the per node number %v over global number %v for node %s", concurrentNum, globalNum, s.nodeName)
|
||||
}
|
||||
|
||||
return concurrentNum
|
||||
}
|
||||
|
||||
@@ -17,16 +17,22 @@ package nodeagent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/nodeagent"
|
||||
testutil "github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
@@ -107,3 +113,259 @@ func Test_validatePodVolumesHostPath(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getDataPathConcurrentNum(t *testing.T) {
|
||||
defaultNum := 100001
|
||||
globalNum := 6
|
||||
nodeName := "node-agent-node"
|
||||
node1 := builder.ForNode("node-agent-node").Result()
|
||||
node2 := builder.ForNode("node-agent-node").Labels(map[string]string{
|
||||
"host-name": "node-1",
|
||||
"xxxx": "yyyyy",
|
||||
}).Result()
|
||||
|
||||
invalidLabelSelector := metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"inva/lid": "inva/lid",
|
||||
},
|
||||
}
|
||||
validLabelSelector1 := metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"host-name": "node-1",
|
||||
},
|
||||
}
|
||||
validLabelSelector2 := metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"xxxx": "yyyyy",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
getFunc func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error)
|
||||
setKubeClient bool
|
||||
kubeClientObj []runtime.Object
|
||||
expectNum int
|
||||
expectLog string
|
||||
}{
|
||||
{
|
||||
name: "failed to get configs",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return nil, errors.New("fake-get-error")
|
||||
},
|
||||
expectLog: "Failed to get node agent configs",
|
||||
expectNum: defaultNum,
|
||||
},
|
||||
{
|
||||
name: "configs cm not found",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return nil, nil
|
||||
},
|
||||
expectLog: fmt.Sprintf("Concurrency configs are not found, use the default number %v", defaultNum),
|
||||
expectNum: defaultNum,
|
||||
},
|
||||
{
|
||||
name: "configs cm's data path concurrency is nil",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return &nodeagent.Configs{}, nil
|
||||
},
|
||||
expectLog: fmt.Sprintf("Concurrency configs are not found, use the default number %v", defaultNum),
|
||||
expectNum: defaultNum,
|
||||
},
|
||||
{
|
||||
name: "global number is invalid",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return &nodeagent.Configs{
|
||||
DataPathConcurrency: &nodeagent.DataPathConcurrency{
|
||||
GlobalConfig: -1,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectLog: fmt.Sprintf("Global number %v is invalid, use the default value %v", -1, defaultNum),
|
||||
expectNum: defaultNum,
|
||||
},
|
||||
{
|
||||
name: "global number is valid",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return &nodeagent.Configs{
|
||||
DataPathConcurrency: &nodeagent.DataPathConcurrency{
|
||||
GlobalConfig: globalNum,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectNum: globalNum,
|
||||
},
|
||||
{
|
||||
name: "node is not found",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return &nodeagent.Configs{
|
||||
DataPathConcurrency: &nodeagent.DataPathConcurrency{
|
||||
GlobalConfig: globalNum,
|
||||
PerNodeConfig: []nodeagent.RuledConfigs{
|
||||
{
|
||||
Number: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
setKubeClient: true,
|
||||
expectLog: fmt.Sprintf("Failed to get node info for %s, use the global number %v", nodeName, globalNum),
|
||||
expectNum: globalNum,
|
||||
},
|
||||
{
|
||||
name: "failed to get selector",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return &nodeagent.Configs{
|
||||
DataPathConcurrency: &nodeagent.DataPathConcurrency{
|
||||
GlobalConfig: globalNum,
|
||||
PerNodeConfig: []nodeagent.RuledConfigs{
|
||||
{
|
||||
NodeSelector: invalidLabelSelector,
|
||||
Number: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
setKubeClient: true,
|
||||
kubeClientObj: []runtime.Object{node1},
|
||||
expectLog: fmt.Sprintf("Failed to parse rule with label selector %s, skip it", invalidLabelSelector.String()),
|
||||
expectNum: globalNum,
|
||||
},
|
||||
{
|
||||
name: "rule number is invalid",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return &nodeagent.Configs{
|
||||
DataPathConcurrency: &nodeagent.DataPathConcurrency{
|
||||
GlobalConfig: globalNum,
|
||||
PerNodeConfig: []nodeagent.RuledConfigs{
|
||||
{
|
||||
NodeSelector: validLabelSelector1,
|
||||
Number: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
setKubeClient: true,
|
||||
kubeClientObj: []runtime.Object{node1},
|
||||
expectLog: fmt.Sprintf("Rule with label selector %s is with an invalid number %v, skip it", validLabelSelector1.String(), -1),
|
||||
expectNum: globalNum,
|
||||
},
|
||||
{
|
||||
name: "label doesn't match",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return &nodeagent.Configs{
|
||||
DataPathConcurrency: &nodeagent.DataPathConcurrency{
|
||||
GlobalConfig: globalNum,
|
||||
PerNodeConfig: []nodeagent.RuledConfigs{
|
||||
{
|
||||
NodeSelector: validLabelSelector1,
|
||||
Number: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
setKubeClient: true,
|
||||
kubeClientObj: []runtime.Object{node1},
|
||||
expectLog: fmt.Sprintf("Per node number for node %s is not found, use the global number %v", nodeName, globalNum),
|
||||
expectNum: globalNum,
|
||||
},
|
||||
{
|
||||
name: "match one rule",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return &nodeagent.Configs{
|
||||
DataPathConcurrency: &nodeagent.DataPathConcurrency{
|
||||
GlobalConfig: globalNum,
|
||||
PerNodeConfig: []nodeagent.RuledConfigs{
|
||||
{
|
||||
NodeSelector: validLabelSelector1,
|
||||
Number: 66,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
setKubeClient: true,
|
||||
kubeClientObj: []runtime.Object{node2},
|
||||
expectLog: fmt.Sprintf("Use the per node number %v over global number %v for node %s", 66, globalNum, nodeName),
|
||||
expectNum: 66,
|
||||
},
|
||||
{
|
||||
name: "match multiple rules",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return &nodeagent.Configs{
|
||||
DataPathConcurrency: &nodeagent.DataPathConcurrency{
|
||||
GlobalConfig: globalNum,
|
||||
PerNodeConfig: []nodeagent.RuledConfigs{
|
||||
{
|
||||
NodeSelector: validLabelSelector1,
|
||||
Number: 66,
|
||||
},
|
||||
{
|
||||
NodeSelector: validLabelSelector2,
|
||||
Number: 36,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
setKubeClient: true,
|
||||
kubeClientObj: []runtime.Object{node2},
|
||||
expectLog: fmt.Sprintf("Use the per node number %v over global number %v for node %s", 36, globalNum, nodeName),
|
||||
expectNum: 36,
|
||||
},
|
||||
{
|
||||
name: "match multiple rules 2",
|
||||
getFunc: func(context.Context, string, kubernetes.Interface) (*nodeagent.Configs, error) {
|
||||
return &nodeagent.Configs{
|
||||
DataPathConcurrency: &nodeagent.DataPathConcurrency{
|
||||
GlobalConfig: globalNum,
|
||||
PerNodeConfig: []nodeagent.RuledConfigs{
|
||||
{
|
||||
NodeSelector: validLabelSelector1,
|
||||
Number: 36,
|
||||
},
|
||||
{
|
||||
NodeSelector: validLabelSelector2,
|
||||
Number: 66,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
setKubeClient: true,
|
||||
kubeClientObj: []runtime.Object{node2},
|
||||
expectLog: fmt.Sprintf("Use the per node number %v over global number %v for node %s", 36, globalNum, nodeName),
|
||||
expectNum: 36,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
fakeKubeClient := fake.NewSimpleClientset(test.kubeClientObj...)
|
||||
|
||||
logBuffer := ""
|
||||
|
||||
s := &nodeAgentServer{
|
||||
nodeName: nodeName,
|
||||
logger: testutil.NewSingleLogger(&logBuffer),
|
||||
}
|
||||
|
||||
if test.setKubeClient {
|
||||
s.kubeClient = fakeKubeClient
|
||||
}
|
||||
|
||||
getConfigsFunc = test.getFunc
|
||||
|
||||
num := s.getDataPathConcurrentNum(defaultNum)
|
||||
assert.Equal(t, test.expectNum, num)
|
||||
if test.expectLog == "" {
|
||||
assert.Equal(t, "", logBuffer)
|
||||
} else {
|
||||
assert.True(t, strings.Contains(logBuffer, test.expectLog))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+11
-3
@@ -21,6 +21,8 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
@@ -38,19 +40,25 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command {
|
||||
err := output.ValidateFlags(c)
|
||||
cmd.CheckError(err)
|
||||
|
||||
veleroClient, err := f.Client()
|
||||
crClient, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
var repos *api.BackupRepositoryList
|
||||
if len(args) > 0 {
|
||||
repos = new(api.BackupRepositoryList)
|
||||
for _, name := range args {
|
||||
repo, err := veleroClient.VeleroV1().BackupRepositories(f.Namespace()).Get(context.TODO(), name, metav1.GetOptions{})
|
||||
repo := new(api.BackupRepository)
|
||||
err := crClient.Get(context.TODO(), ctrlclient.ObjectKey{Namespace: f.Namespace(), Name: name}, repo)
|
||||
cmd.CheckError(err)
|
||||
repos.Items = append(repos.Items, *repo)
|
||||
}
|
||||
} else {
|
||||
repos, err = veleroClient.VeleroV1().BackupRepositories(f.Namespace()).List(context.TODO(), listOptions)
|
||||
selector := labels.NewSelector()
|
||||
if listOptions.LabelSelector != "" {
|
||||
selector, err = labels.Parse(listOptions.LabelSelector)
|
||||
cmd.CheckError(err)
|
||||
}
|
||||
err = crClient.List(context.TODO(), repos, &ctrlclient.ListOptions{LabelSelector: selector})
|
||||
cmd.CheckError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
@@ -49,13 +49,11 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
restoreName := args[0]
|
||||
|
||||
veleroClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
kbClient, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
restore, err := veleroClient.VeleroV1().Restores(f.Namespace()).Get(context.TODO(), restoreName, metav1.GetOptions{})
|
||||
restore := new(velerov1api.Restore)
|
||||
err = kbClient.Get(context.TODO(), ctrlclient.ObjectKey{Namespace: f.Namespace(), Name: restoreName}, restore)
|
||||
if apierrors.IsNotFound(err) {
|
||||
cmd.Exit("Restore %q does not exist.", restoreName)
|
||||
} else if err != nil {
|
||||
|
||||
@@ -115,7 +115,7 @@ func (o *CreateOptions) Complete(args []string, f client.Factory) error {
|
||||
func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
var orders map[string]string
|
||||
|
||||
veleroClient, err := f.Client()
|
||||
crClient, err := f.KubebuilderClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -171,7 +171,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = veleroClient.VeleroV1().Schedules(schedule.Namespace).Create(context.TODO(), schedule, metav1.CreateOptions{})
|
||||
err = crClient.Create(context.TODO(), schedule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
@@ -36,19 +38,25 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||
Use: use + " [NAME1] [NAME2] [NAME...]",
|
||||
Short: "Describe schedules",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
veleroClient, err := f.Client()
|
||||
crClient, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
var schedules *v1.ScheduleList
|
||||
if len(args) > 0 {
|
||||
schedules = new(v1.ScheduleList)
|
||||
for _, name := range args {
|
||||
schedule, err := veleroClient.VeleroV1().Schedules(f.Namespace()).Get(context.TODO(), name, metav1.GetOptions{})
|
||||
schedule := new(v1.Schedule)
|
||||
err := crClient.Get(context.TODO(), ctrlclient.ObjectKey{Namespace: f.Namespace(), Name: name}, schedule)
|
||||
cmd.CheckError(err)
|
||||
schedules.Items = append(schedules.Items, *schedule)
|
||||
}
|
||||
} else {
|
||||
schedules, err = veleroClient.VeleroV1().Schedules(f.Namespace()).List(context.TODO(), listOptions)
|
||||
selector := labels.NewSelector()
|
||||
if listOptions.LabelSelector != "" {
|
||||
selector, err = labels.Parse(listOptions.LabelSelector)
|
||||
cmd.CheckError(err)
|
||||
}
|
||||
err = crClient.List(context.TODO(), schedules, &ctrlclient.ListOptions{LabelSelector: selector})
|
||||
cmd.CheckError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
@@ -38,19 +40,25 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command {
|
||||
err := output.ValidateFlags(c)
|
||||
cmd.CheckError(err)
|
||||
|
||||
veleroClient, err := f.Client()
|
||||
crClient, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
var schedules *api.ScheduleList
|
||||
if len(args) > 0 {
|
||||
schedules = new(api.ScheduleList)
|
||||
for _, name := range args {
|
||||
schedule, err := veleroClient.VeleroV1().Schedules(f.Namespace()).Get(context.TODO(), name, metav1.GetOptions{})
|
||||
schedule := new(api.Schedule)
|
||||
err := crClient.Get(context.TODO(), ctrlclient.ObjectKey{Name: name, Namespace: f.Namespace()}, schedule)
|
||||
cmd.CheckError(err)
|
||||
schedules.Items = append(schedules.Items, *schedule)
|
||||
}
|
||||
} else {
|
||||
schedules, err = veleroClient.VeleroV1().Schedules(f.Namespace()).List(context.TODO(), listOptions)
|
||||
selector := labels.NewSelector()
|
||||
if listOptions.LabelSelector != "" {
|
||||
selector, err = labels.Parse(listOptions.LabelSelector)
|
||||
cmd.CheckError(err)
|
||||
}
|
||||
err := crClient.List(context.TODO(), schedules, &ctrlclient.ListOptions{LabelSelector: selector})
|
||||
cmd.CheckError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
@@ -63,7 +64,7 @@ func NewPauseCommand(f client.Factory, use string) *cobra.Command {
|
||||
}
|
||||
|
||||
func runPause(f client.Factory, o *cli.SelectOptions, paused bool) error {
|
||||
client, err := f.Client()
|
||||
crClient, err := f.KubebuilderClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -75,7 +76,8 @@ func runPause(f client.Factory, o *cli.SelectOptions, paused bool) error {
|
||||
switch {
|
||||
case len(o.Names) > 0:
|
||||
for _, name := range o.Names {
|
||||
schedule, err := client.VeleroV1().Schedules(f.Namespace()).Get(context.TODO(), name, metav1.GetOptions{})
|
||||
schedule := new(velerov1api.Schedule)
|
||||
err := crClient.Get(context.TODO(), ctrlclient.ObjectKey{Name: name, Namespace: f.Namespace()}, schedule)
|
||||
if err != nil {
|
||||
errs = append(errs, errors.WithStack(err))
|
||||
continue
|
||||
@@ -83,11 +85,16 @@ func runPause(f client.Factory, o *cli.SelectOptions, paused bool) error {
|
||||
schedules = append(schedules, schedule)
|
||||
}
|
||||
default:
|
||||
selector := labels.Everything().String()
|
||||
selector := labels.Everything()
|
||||
if o.Selector.LabelSelector != nil {
|
||||
selector = o.Selector.String()
|
||||
convertedSelector, err := metav1.LabelSelectorAsSelector(o.Selector.LabelSelector)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
selector = convertedSelector
|
||||
}
|
||||
res, err := client.VeleroV1().Schedules(f.Namespace()).List(context.TODO(), metav1.ListOptions{
|
||||
res := new(velerov1api.ScheduleList)
|
||||
err := crClient.List(context.TODO(), res, &ctrlclient.ListOptions{
|
||||
LabelSelector: selector,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -113,7 +120,7 @@ func runPause(f client.Factory, o *cli.SelectOptions, paused bool) error {
|
||||
continue
|
||||
}
|
||||
schedule.Spec.Paused = paused
|
||||
if _, err := client.VeleroV1().Schedules(schedule.Namespace).Update(context.TODO(), schedule, metav1.UpdateOptions{}); err != nil {
|
||||
if err := crClient.Update(context.TODO(), schedule); err != nil {
|
||||
return errors.Wrapf(err, "failed to update schedule %s", schedule.Name)
|
||||
}
|
||||
fmt.Printf("Schedule %s %s successfully\n", schedule.Name, msg)
|
||||
|
||||
@@ -124,12 +124,12 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := f.Client()
|
||||
client, err := f.KubebuilderClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := client.VeleroV1().VolumeSnapshotLocations(volumeSnapshotLocation.Namespace).Create(context.TODO(), volumeSnapshotLocation, metav1.CreateOptions{}); err != nil {
|
||||
if err := client.Create(context.TODO(), volumeSnapshotLocation); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
@@ -36,18 +37,19 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command {
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
err := output.ValidateFlags(c)
|
||||
cmd.CheckError(err)
|
||||
veleroClient, err := f.Client()
|
||||
client, err := f.KubebuilderClient()
|
||||
cmd.CheckError(err)
|
||||
var locations *api.VolumeSnapshotLocationList
|
||||
locations := new(api.VolumeSnapshotLocationList)
|
||||
|
||||
if len(args) > 0 {
|
||||
locations = new(api.VolumeSnapshotLocationList)
|
||||
for _, name := range args {
|
||||
location, err := veleroClient.VeleroV1().VolumeSnapshotLocations(f.Namespace()).Get(context.TODO(), name, metav1.GetOptions{})
|
||||
location := new(api.VolumeSnapshotLocation)
|
||||
err := client.Get(context.TODO(), kbclient.ObjectKey{Namespace: f.Namespace(), Name: name}, location)
|
||||
cmd.CheckError(err)
|
||||
locations.Items = append(locations.Items, *location)
|
||||
}
|
||||
} else {
|
||||
locations, err = veleroClient.VeleroV1().VolumeSnapshotLocations(f.Namespace()).List(context.TODO(), listOptions)
|
||||
err = client.List(context.TODO(), locations, &kbclient.ListOptions{Namespace: f.Namespace()})
|
||||
cmd.CheckError(err)
|
||||
}
|
||||
_, err = output.PrintWithFormat(c, locations)
|
||||
|
||||
@@ -152,12 +152,12 @@ func newPodVolumeRestoreItemAction(f client.Factory) plugincommon.HandlerInitial
|
||||
return nil, err
|
||||
}
|
||||
|
||||
veleroClient, err := f.Client()
|
||||
crClient, err := f.KubebuilderClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return restore.NewPodVolumeRestoreAction(logger, client.CoreV1().ConfigMaps(f.Namespace()), veleroClient.VeleroV1().PodVolumeBackups(f.Namespace())), nil
|
||||
return restore.NewPodVolumeRestoreAction(logger, client.CoreV1().ConfigMaps(f.Namespace()), crClient), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+50
-27
@@ -66,7 +66,6 @@ import (
|
||||
"github.com/vmware-tanzu/velero/pkg/controller"
|
||||
velerodiscovery "github.com/vmware-tanzu/velero/pkg/discovery"
|
||||
"github.com/vmware-tanzu/velero/pkg/features"
|
||||
clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperationmap"
|
||||
"github.com/vmware-tanzu/velero/pkg/metrics"
|
||||
"github.com/vmware-tanzu/velero/pkg/nodeagent"
|
||||
@@ -239,7 +238,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).")
|
||||
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
|
||||
}
|
||||
@@ -249,12 +248,12 @@ type server struct {
|
||||
metricsAddress string
|
||||
kubeClientConfig *rest.Config
|
||||
kubeClient kubernetes.Interface
|
||||
veleroClient clientset.Interface
|
||||
discoveryClient discovery.DiscoveryInterface
|
||||
discoveryHelper velerodiscovery.Helper
|
||||
dynamicClient dynamic.Interface
|
||||
csiSnapshotClient *snapshotv1client.Clientset
|
||||
csiSnapshotLister snapshotv1listers.VolumeSnapshotLister
|
||||
crClient ctrlclient.Client
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
logger logrus.FieldLogger
|
||||
@@ -268,6 +267,7 @@ type server struct {
|
||||
mgr manager.Manager
|
||||
credentialFileStore credentials.FileStore
|
||||
credentialSecretStore credentials.SecretStore
|
||||
featureVerifier features.Verifier
|
||||
}
|
||||
|
||||
func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*server, error) {
|
||||
@@ -294,12 +294,12 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
veleroClient, err := f.Client()
|
||||
dynamicClient, err := f.DynamicClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dynamicClient, err := f.DynamicClient()
|
||||
crClient, err := f.KubebuilderClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -309,6 +309,12 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
featureVerifier := features.NewVerifier(pluginRegistry)
|
||||
|
||||
if _, err := featureVerifier.Verify(velerov1api.CSIFeatureFlag); err != nil {
|
||||
logger.WithError(err).Warn("CSI feature verification failed, the feature may not be ready.")
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -367,14 +373,20 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var discoveryClient *discovery.DiscoveryClient
|
||||
if discoveryClient, err = discovery.NewDiscoveryClientForConfig(clientConfig); err != nil {
|
||||
cancelFunc()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &server{
|
||||
namespace: f.Namespace(),
|
||||
metricsAddress: config.metricsAddress,
|
||||
kubeClientConfig: clientConfig,
|
||||
kubeClient: kubeClient,
|
||||
veleroClient: veleroClient,
|
||||
discoveryClient: veleroClient.Discovery(),
|
||||
discoveryClient: discoveryClient,
|
||||
dynamicClient: dynamicClient,
|
||||
crClient: crClient,
|
||||
ctx: ctx,
|
||||
cancelFunc: cancelFunc,
|
||||
logger: logger,
|
||||
@@ -384,6 +396,7 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
|
||||
mgr: mgr,
|
||||
credentialFileStore: credentialFileStore,
|
||||
credentialSecretStore: credentialSecretStore,
|
||||
featureVerifier: featureVerifier,
|
||||
}
|
||||
|
||||
// Setup CSI snapshot client and lister
|
||||
@@ -528,7 +541,7 @@ High priorities:
|
||||
- PVs go before PVCs because PVCs depend on them.
|
||||
- PVCs go before pods or controllers so they can be mounted as volumes.
|
||||
- Service accounts go before secrets so service account token secrets can be filled automatically.
|
||||
- Secrets and config maps go before pods or controllers so they can be mounted
|
||||
- Secrets and ConfigMaps go before pods or controllers so they can be mounted
|
||||
as volumes.
|
||||
- Limit ranges go before pods or controllers so pods can use them.
|
||||
- Pods go before controllers so they can be explicitly restored and potentially
|
||||
@@ -719,6 +732,11 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
s.logger.Fatal(err, "unable to create controller", "controller", controller.BackupStorageLocation)
|
||||
}
|
||||
|
||||
pvbInformer, err := s.mgr.GetCache().GetInformer(s.ctx, &velerov1api.PodVolumeBackup{})
|
||||
if err != nil {
|
||||
s.logger.Fatal(err, "fail to get controller-runtime informer from manager for PVB")
|
||||
}
|
||||
|
||||
if _, ok := enabledRuntimeControllers[controller.Backup]; ok {
|
||||
backupper, err := backup.NewKubernetesBackupper(
|
||||
s.mgr.GetClient(),
|
||||
@@ -728,10 +746,8 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
podvolume.NewBackupperFactory(
|
||||
s.repoLocker,
|
||||
s.repoEnsurer,
|
||||
s.veleroClient,
|
||||
s.kubeClient.CoreV1(),
|
||||
s.kubeClient.CoreV1(),
|
||||
s.kubeClient.CoreV1(),
|
||||
s.crClient,
|
||||
pvbInformer,
|
||||
s.logger,
|
||||
),
|
||||
s.config.podVolumeOperationTimeout,
|
||||
@@ -760,7 +776,6 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
backupStoreGetter,
|
||||
s.config.formatFlag.Parse(),
|
||||
s.csiSnapshotLister,
|
||||
s.csiSnapshotClient,
|
||||
s.credentialFileStore,
|
||||
s.config.maxConcurrentK8SConnections,
|
||||
s.config.defaultSnapshotMoveData,
|
||||
@@ -810,10 +825,8 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
podvolume.NewBackupperFactory(
|
||||
s.repoLocker,
|
||||
s.repoEnsurer,
|
||||
s.veleroClient,
|
||||
s.kubeClient.CoreV1(),
|
||||
s.kubeClient.CoreV1(),
|
||||
s.kubeClient.CoreV1(),
|
||||
s.crClient,
|
||||
pvbInformer,
|
||||
s.logger,
|
||||
),
|
||||
s.config.podVolumeOperationTimeout,
|
||||
@@ -824,6 +837,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
cmd.CheckError(err)
|
||||
r := controller.NewBackupFinalizerReconciler(
|
||||
s.mgr.GetClient(),
|
||||
s.csiSnapshotLister,
|
||||
clock.RealClock{},
|
||||
backupper,
|
||||
newPluginManager,
|
||||
@@ -901,6 +915,11 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
}
|
||||
}
|
||||
|
||||
pvrInformer, err := s.mgr.GetCache().GetInformer(s.ctx, &velerov1api.PodVolumeRestore{})
|
||||
if err != nil {
|
||||
s.logger.Fatal(err, "fail to get controller-runtime informer from manager for PVR")
|
||||
}
|
||||
|
||||
if _, ok := enabledRuntimeControllers[controller.Restore]; ok {
|
||||
restorer, err := restore.NewKubernetesRestorer(
|
||||
s.discoveryHelper,
|
||||
@@ -910,10 +929,9 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
podvolume.NewRestorerFactory(
|
||||
s.repoLocker,
|
||||
s.repoEnsurer,
|
||||
s.veleroClient,
|
||||
s.kubeClient.CoreV1(),
|
||||
s.kubeClient.CoreV1(),
|
||||
s.kubeClient,
|
||||
s.crClient,
|
||||
pvrInformer,
|
||||
s.logger,
|
||||
),
|
||||
s.config.podVolumeOperationTimeout,
|
||||
@@ -924,6 +942,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
s.kubeClient.CoreV1().RESTClient(),
|
||||
s.credentialFileStore,
|
||||
s.mgr.GetClient(),
|
||||
s.featureVerifier,
|
||||
)
|
||||
|
||||
cmd.CheckError(err)
|
||||
@@ -1062,13 +1081,13 @@ func markInProgressBackupsFailed(ctx context.Context, client ctrlclient.Client,
|
||||
}
|
||||
|
||||
for i, backup := range backups.Items {
|
||||
if backup.Status.Phase != velerov1api.BackupPhaseInProgress && backup.Status.Phase != velerov1api.BackupPhaseWaitingForPluginOperations {
|
||||
if backup.Status.Phase != velerov1api.BackupPhaseInProgress {
|
||||
log.Debugf("the status of backup %q is %q, skip", backup.GetName(), backup.Status.Phase)
|
||||
continue
|
||||
}
|
||||
updated := backup.DeepCopy()
|
||||
updated.Status.Phase = velerov1api.BackupPhaseFailed
|
||||
updated.Status.FailureReason = fmt.Sprintf("found a backup with status %q during the server starting, mark it as %q", velerov1api.BackupPhaseInProgress, updated.Status.Phase)
|
||||
updated.Status.FailureReason = fmt.Sprintf("found a backup with status %q during the server starting, mark it as %q", backup.Status.Phase, updated.Status.Phase)
|
||||
updated.Status.CompletionTimestamp = &metav1.Time{Time: time.Now()}
|
||||
if err := client.Patch(ctx, updated, ctrlclient.MergeFrom(&backups.Items[i])); err != nil {
|
||||
log.WithError(errors.WithStack(err)).Errorf("failed to patch backup %q", backup.GetName())
|
||||
@@ -1086,13 +1105,13 @@ func markInProgressRestoresFailed(ctx context.Context, client ctrlclient.Client,
|
||||
return
|
||||
}
|
||||
for i, restore := range restores.Items {
|
||||
if restore.Status.Phase != velerov1api.RestorePhaseInProgress && restore.Status.Phase != velerov1api.RestorePhaseWaitingForPluginOperations {
|
||||
if restore.Status.Phase != velerov1api.RestorePhaseInProgress {
|
||||
log.Debugf("the status of restore %q is %q, skip", restore.GetName(), restore.Status.Phase)
|
||||
continue
|
||||
}
|
||||
updated := restore.DeepCopy()
|
||||
updated.Status.Phase = velerov1api.RestorePhaseFailed
|
||||
updated.Status.FailureReason = fmt.Sprintf("found a restore with status %q during the server starting, mark it as %q", velerov1api.RestorePhaseInProgress, updated.Status.Phase)
|
||||
updated.Status.FailureReason = fmt.Sprintf("found a restore with status %q during the server starting, mark it as %q", restore.Status.Phase, updated.Status.Phase)
|
||||
updated.Status.CompletionTimestamp = &metav1.Time{Time: time.Now()}
|
||||
if err := client.Patch(ctx, updated, ctrlclient.MergeFrom(&restores.Items[i])); err != nil {
|
||||
log.WithError(errors.WithStack(err)).Errorf("failed to patch restore %q", restore.GetName())
|
||||
@@ -1115,7 +1134,9 @@ func markDataUploadsCancel(ctx context.Context, client ctrlclient.Client, backup
|
||||
du := dataUploads.Items[i]
|
||||
if du.Status.Phase == velerov2alpha1api.DataUploadPhaseAccepted ||
|
||||
du.Status.Phase == velerov2alpha1api.DataUploadPhasePrepared ||
|
||||
du.Status.Phase == velerov2alpha1api.DataUploadPhaseInProgress {
|
||||
du.Status.Phase == velerov2alpha1api.DataUploadPhaseInProgress ||
|
||||
du.Status.Phase == velerov2alpha1api.DataUploadPhaseNew ||
|
||||
du.Status.Phase == "" {
|
||||
err := controller.UpdateDataUploadWithRetry(ctx, client, types.NamespacedName{Namespace: du.Namespace, Name: du.Name}, log.WithField("dataupload", du.Name),
|
||||
func(dataUpload *velerov2alpha1api.DataUpload) {
|
||||
dataUpload.Spec.Cancel = true
|
||||
@@ -1143,7 +1164,9 @@ func markDataDownloadsCancel(ctx context.Context, client ctrlclient.Client, rest
|
||||
dd := dataDownloads.Items[i]
|
||||
if dd.Status.Phase == velerov2alpha1api.DataDownloadPhaseAccepted ||
|
||||
dd.Status.Phase == velerov2alpha1api.DataDownloadPhasePrepared ||
|
||||
dd.Status.Phase == velerov2alpha1api.DataDownloadPhaseInProgress {
|
||||
dd.Status.Phase == velerov2alpha1api.DataDownloadPhaseInProgress ||
|
||||
dd.Status.Phase == velerov2alpha1api.DataDownloadPhaseNew ||
|
||||
dd.Status.Phase == "" {
|
||||
err := controller.UpdateDataDownloadWithRetry(ctx, client, types.NamespacedName{Namespace: dd.Namespace, Name: dd.Name}, log.WithField("datadownload", dd.Name),
|
||||
func(dataDownload *velerov2alpha1api.DataDownload) {
|
||||
dataDownload.Spec.Cancel = true
|
||||
@@ -1151,7 +1174,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"
|
||||
@@ -33,7 +34,6 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/utils/clock"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
@@ -111,7 +111,6 @@ func NewBackupReconciler(
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter,
|
||||
formatFlag logging.Format,
|
||||
volumeSnapshotLister snapshotv1listers.VolumeSnapshotLister,
|
||||
volumeSnapshotClient snapshotterClientSet.Interface,
|
||||
credentialStore credentials.FileStore,
|
||||
maxConcurrentK8SConnections int,
|
||||
defaultSnapshotMoveData bool,
|
||||
@@ -137,7 +136,6 @@ func NewBackupReconciler(
|
||||
backupStoreGetter: backupStoreGetter,
|
||||
formatFlag: formatFlag,
|
||||
volumeSnapshotLister: volumeSnapshotLister,
|
||||
volumeSnapshotClient: volumeSnapshotClient,
|
||||
credentialFileStore: credentialStore,
|
||||
maxConcurrentK8SConnections: maxConcurrentK8SConnections,
|
||||
defaultSnapshotMoveData: defaultSnapshotMoveData,
|
||||
@@ -476,7 +474,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 {
|
||||
@@ -659,65 +657,15 @@ func (b *backupReconciler) runBackup(backup *pkgbackup.Request) error {
|
||||
fatalErrs = append(fatalErrs, err)
|
||||
}
|
||||
|
||||
// Empty slices here so that they can be passed in to the persistBackup call later, regardless of whether or not CSI's enabled.
|
||||
// This way, we only make the Lister call if the feature flag's on.
|
||||
var volumeSnapshots []snapshotv1api.VolumeSnapshot
|
||||
var volumeSnapshotContents []snapshotv1api.VolumeSnapshotContent
|
||||
var volumeSnapshotClasses []snapshotv1api.VolumeSnapshotClass
|
||||
if boolptr.IsSetToTrue(backup.Spec.SnapshotMoveData) {
|
||||
backupLog.Info("backup SnapshotMoveData is set to true, skip VolumeSnapshot resource persistence.")
|
||||
} else if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
||||
selector := label.NewSelectorForBackup(backup.Name)
|
||||
vscList := &snapshotv1api.VolumeSnapshotContentList{}
|
||||
|
||||
if b.volumeSnapshotLister != nil {
|
||||
tmpVSs, err := b.volumeSnapshotLister.List(label.NewSelectorForBackup(backup.Name))
|
||||
if err != nil {
|
||||
backupLog.Error(err)
|
||||
}
|
||||
for _, vs := range tmpVSs {
|
||||
volumeSnapshots = append(volumeSnapshots, *vs)
|
||||
}
|
||||
}
|
||||
|
||||
backup.CSISnapshots = volumeSnapshots
|
||||
|
||||
err = b.kbClient.List(context.Background(), vscList, &kbclient.ListOptions{LabelSelector: selector})
|
||||
if err != nil {
|
||||
backupLog.Error(err)
|
||||
}
|
||||
if len(vscList.Items) >= 0 {
|
||||
volumeSnapshotContents = vscList.Items
|
||||
}
|
||||
|
||||
vsClassSet := sets.NewString()
|
||||
for index := range volumeSnapshotContents {
|
||||
// persist the volumesnapshotclasses referenced by vsc
|
||||
if volumeSnapshotContents[index].Spec.VolumeSnapshotClassName != nil && !vsClassSet.Has(*volumeSnapshotContents[index].Spec.VolumeSnapshotClassName) {
|
||||
vsClass := &snapshotv1api.VolumeSnapshotClass{}
|
||||
if err := b.kbClient.Get(context.TODO(), kbclient.ObjectKey{Name: *volumeSnapshotContents[index].Spec.VolumeSnapshotClassName}, vsClass); err != nil {
|
||||
backupLog.Error(err)
|
||||
} else {
|
||||
vsClassSet.Insert(*volumeSnapshotContents[index].Spec.VolumeSnapshotClassName)
|
||||
volumeSnapshotClasses = append(volumeSnapshotClasses, *vsClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// native snapshots phase will either be failed or completed right away
|
||||
// https://github.com/vmware-tanzu/velero/blob/de3ea52f0cc478e99efa7b9524c7f353514261a4/pkg/backup/item_backupper.go#L632-L639
|
||||
backup.Status.VolumeSnapshotsAttempted = len(backup.VolumeSnapshots)
|
||||
for _, snap := range backup.VolumeSnapshots {
|
||||
if snap.Status.Phase == volume.SnapshotPhaseCompleted {
|
||||
backup.Status.VolumeSnapshotsCompleted++
|
||||
}
|
||||
}
|
||||
|
||||
backup.Status.CSIVolumeSnapshotsAttempted = len(backup.CSISnapshots)
|
||||
for _, vs := range backup.CSISnapshots {
|
||||
if vs.Status != nil && boolptr.IsSetToTrue(vs.Status.ReadyToUse) {
|
||||
backup.Status.CSIVolumeSnapshotsCompleted++
|
||||
}
|
||||
}
|
||||
volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses := pkgbackup.UpdateBackupCSISnapshotsStatus(b.kbClient, b.volumeSnapshotLister, backup.Backup, backupLog)
|
||||
|
||||
// Iterate over backup item operations and update progress.
|
||||
// Any errors on operations at this point should be added to backup errors.
|
||||
@@ -848,6 +796,7 @@ func persistBackup(backup *pkgbackup.Request,
|
||||
) []error {
|
||||
persistErrs := []error{}
|
||||
backupJSON := new(bytes.Buffer)
|
||||
volumeInfos := make([]volume.VolumeInfo, 0)
|
||||
|
||||
if err := encode.To(backup.Backup, "json", backupJSON); err != nil {
|
||||
persistErrs = append(persistErrs, errors.Wrap(err, "error encoding backup"))
|
||||
@@ -894,6 +843,11 @@ func persistBackup(backup *pkgbackup.Request,
|
||||
persistErrs = append(persistErrs, errs...)
|
||||
}
|
||||
|
||||
volumeInfoJSON, errs := encode.ToJSONGzip(volumeInfos, "backup volumes information")
|
||||
if errs != nil {
|
||||
persistErrs = append(persistErrs, errs...)
|
||||
}
|
||||
|
||||
if len(persistErrs) > 0 {
|
||||
// Don't upload the JSON files or backup tarball if encoding to json fails.
|
||||
backupJSON = nil
|
||||
@@ -905,6 +859,7 @@ func persistBackup(backup *pkgbackup.Request,
|
||||
csiSnapshotContentsJSON = nil
|
||||
csiSnapshotClassesJSON = nil
|
||||
backupResult = nil
|
||||
volumeInfoJSON = nil
|
||||
}
|
||||
|
||||
backupInfo := persistence.BackupInfo{
|
||||
@@ -920,6 +875,7 @@ func persistBackup(backup *pkgbackup.Request,
|
||||
CSIVolumeSnapshots: csiSnapshotJSON,
|
||||
CSIVolumeSnapshotContents: csiSnapshotContentsJSON,
|
||||
CSIVolumeSnapshotClasses: csiSnapshotClassesJSON,
|
||||
BackupVolumeInfo: volumeInfoJSON,
|
||||
}
|
||||
if err := backupStore.PutBackup(backupInfo); err != nil {
|
||||
persistErrs = append(persistErrs, err)
|
||||
|
||||
@@ -21,11 +21,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
snapshotfake "github.com/kubernetes-csi/external-snapshotter/client/v4/clientset/versioned/fake"
|
||||
snapshotinformers "github.com/kubernetes-csi/external-snapshotter/client/v4/informers/externalversions"
|
||||
@@ -43,6 +45,10 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
|
||||
fakeClient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
@@ -1665,3 +1671,63 @@ func Test_getLastSuccessBySchedule(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Unit tests to make sure that the backup's status is updated correctly during reconcile.
|
||||
// To clear up confusion whether status can be updated with Patch alone without status writer and not kbClient.Status().Patch()
|
||||
func TestPatchResourceWorksWithStatus(t *testing.T) {
|
||||
type args struct {
|
||||
original *velerov1api.Backup
|
||||
updated *velerov1api.Backup
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "patch backup status",
|
||||
args: args{
|
||||
original: defaultBackup().SnapshotMoveData(false).Result(),
|
||||
updated: defaultBackup().SnapshotMoveData(false).WithStatus(velerov1api.BackupStatus{
|
||||
CSIVolumeSnapshotsCompleted: 1,
|
||||
}).Result(),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
error := velerov1api.AddToScheme(scheme)
|
||||
if error != nil {
|
||||
t.Errorf("PatchResource() error = %v", error)
|
||||
}
|
||||
fakeClient := fakeClient.NewClientBuilder().WithScheme(scheme).WithObjects(tt.args.original).Build()
|
||||
fromCluster := &velerov1api.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: tt.args.original.Name,
|
||||
Namespace: tt.args.original.Namespace,
|
||||
},
|
||||
}
|
||||
// check original exists
|
||||
if err := fakeClient.Get(context.Background(), kbclient.ObjectKeyFromObject(tt.args.updated), fromCluster); err != nil {
|
||||
t.Errorf("PatchResource() error = %v", err)
|
||||
}
|
||||
// ignore resourceVersion
|
||||
tt.args.updated.ResourceVersion = fromCluster.ResourceVersion
|
||||
tt.args.original.ResourceVersion = fromCluster.ResourceVersion
|
||||
if err := kubeutil.PatchResource(tt.args.original, tt.args.updated, fakeClient); (err != nil) != tt.wantErr {
|
||||
t.Errorf("PatchResource() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
// check updated exists
|
||||
if err := fakeClient.Get(context.Background(), kbclient.ObjectKeyFromObject(tt.args.updated), fromCluster); err != nil {
|
||||
t.Errorf("PatchResource() error = %v", err)
|
||||
}
|
||||
|
||||
// check fromCluster is equal to updated
|
||||
if !reflect.DeepEqual(fromCluster, tt.args.updated) {
|
||||
t.Error(cmp.Diff(fromCluster, tt.args.updated))
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v4/listers/volumesnapshot/v1"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
||||
"github.com/vmware-tanzu/velero/pkg/metrics"
|
||||
@@ -40,19 +42,21 @@ import (
|
||||
|
||||
// backupFinalizerReconciler reconciles a Backup object
|
||||
type backupFinalizerReconciler struct {
|
||||
client kbclient.Client
|
||||
clock clocks.WithTickerAndDelayedExecution
|
||||
backupper pkgbackup.Backupper
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
|
||||
backupTracker BackupTracker
|
||||
metrics *metrics.ServerMetrics
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter
|
||||
log logrus.FieldLogger
|
||||
client kbclient.Client
|
||||
volumeSnapshotLister snapshotv1listers.VolumeSnapshotLister
|
||||
clock clocks.WithTickerAndDelayedExecution
|
||||
backupper pkgbackup.Backupper
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
|
||||
backupTracker BackupTracker
|
||||
metrics *metrics.ServerMetrics
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter
|
||||
log logrus.FieldLogger
|
||||
}
|
||||
|
||||
// NewBackupFinalizerReconciler initializes and returns backupFinalizerReconciler struct.
|
||||
func NewBackupFinalizerReconciler(
|
||||
client kbclient.Client,
|
||||
volumeSnapshotLister snapshotv1listers.VolumeSnapshotLister,
|
||||
clock clocks.WithTickerAndDelayedExecution,
|
||||
backupper pkgbackup.Backupper,
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
|
||||
@@ -187,6 +191,7 @@ func (r *backupFinalizerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||
backup.Status.CompletionTimestamp = &metav1.Time{Time: r.clock.Now()}
|
||||
recordBackupMetrics(log, backup, outBackupFile, r.metrics, true)
|
||||
|
||||
pkgbackup.UpdateBackupCSISnapshotsStatus(r.client, r.volumeSnapshotLister, backup, log)
|
||||
// update backup metadata in object store
|
||||
backupJSON := new(bytes.Buffer)
|
||||
if err := encode.To(backup, "json", backupJSON); err != nil {
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v4/listers/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -43,12 +44,14 @@ import (
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
velerotestmocks "github.com/vmware-tanzu/velero/pkg/test/mocks"
|
||||
)
|
||||
|
||||
func mockBackupFinalizerReconciler(fakeClient kbclient.Client, fakeClock *testclocks.FakeClock) (*backupFinalizerReconciler, *fakeBackupper) {
|
||||
func mockBackupFinalizerReconciler(fakeClient kbclient.Client, fakeVolumeSnapshotLister snapshotv1listers.VolumeSnapshotLister, fakeClock *testclocks.FakeClock) (*backupFinalizerReconciler, *fakeBackupper) {
|
||||
backupper := new(fakeBackupper)
|
||||
return NewBackupFinalizerReconciler(
|
||||
fakeClient,
|
||||
fakeVolumeSnapshotLister,
|
||||
fakeClock,
|
||||
backupper,
|
||||
func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager },
|
||||
@@ -160,7 +163,10 @@ func TestBackupFinalizerReconcile(t *testing.T) {
|
||||
}
|
||||
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, initObjs...)
|
||||
reconciler, backupper := mockBackupFinalizerReconciler(fakeClient, fakeClock)
|
||||
|
||||
fakeVolumeSnapshotLister := velerotestmocks.NewVolumeSnapshotLister(t)
|
||||
|
||||
reconciler, backupper := mockBackupFinalizerReconciler(fakeClient, fakeVolumeSnapshotLister, fakeClock)
|
||||
pluginManager.On("CleanupClients").Return(nil)
|
||||
backupStore.On("GetBackupItemOperations", test.backup.Name).Return(test.backupOperations, nil)
|
||||
backupStore.On("GetBackupContents", mock.Anything).Return(io.NopCloser(bytes.NewReader([]byte("hello world"))), nil)
|
||||
|
||||
@@ -19,8 +19,11 @@ package controller
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
v2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -275,6 +278,8 @@ func (c *backupOperationsReconciler) updateBackupAndOperationsJSON(
|
||||
return nil
|
||||
}
|
||||
|
||||
// check progress of backupItemOperations
|
||||
// return: inProgressOperations, changes, completedCount, failedCount, errs
|
||||
func getBackupItemOperationProgress(
|
||||
backup *velerov1api.Backup,
|
||||
pluginManager clientmgmt.Manager,
|
||||
@@ -291,7 +296,7 @@ func getBackupItemOperationProgress(
|
||||
if err != nil {
|
||||
operation.Status.Phase = itemoperation.OperationPhaseFailed
|
||||
operation.Status.Error = err.Error()
|
||||
errs = append(errs, err.Error())
|
||||
errs = append(errs, wrapErrMsg(err.Error(), bia))
|
||||
changes = true
|
||||
failedCount++
|
||||
continue
|
||||
@@ -300,7 +305,7 @@ func getBackupItemOperationProgress(
|
||||
if err != nil {
|
||||
operation.Status.Phase = itemoperation.OperationPhaseFailed
|
||||
operation.Status.Error = err.Error()
|
||||
errs = append(errs, err.Error())
|
||||
errs = append(errs, wrapErrMsg(err.Error(), bia))
|
||||
changes = true
|
||||
failedCount++
|
||||
continue
|
||||
@@ -338,7 +343,7 @@ func getBackupItemOperationProgress(
|
||||
if operationProgress.Err != "" {
|
||||
operation.Status.Phase = itemoperation.OperationPhaseFailed
|
||||
operation.Status.Error = operationProgress.Err
|
||||
errs = append(errs, operationProgress.Err)
|
||||
errs = append(errs, wrapErrMsg(operationProgress.Err, bia))
|
||||
changes = true
|
||||
failedCount++
|
||||
continue
|
||||
@@ -353,7 +358,7 @@ func getBackupItemOperationProgress(
|
||||
_ = bia.Cancel(operation.Spec.OperationID, backup)
|
||||
operation.Status.Phase = itemoperation.OperationPhaseFailed
|
||||
operation.Status.Error = "Asynchronous action timed out"
|
||||
errs = append(errs, operation.Status.Error)
|
||||
errs = append(errs, wrapErrMsg(operation.Status.Error, bia))
|
||||
changes = true
|
||||
failedCount++
|
||||
continue
|
||||
@@ -373,3 +378,15 @@ func getBackupItemOperationProgress(
|
||||
}
|
||||
return inProgressOperations, changes, completedCount, failedCount, errs
|
||||
}
|
||||
|
||||
// wrap the error message to include the BIA name
|
||||
func wrapErrMsg(errMsg string, bia v2.BackupItemAction) string {
|
||||
plugin := "unknown"
|
||||
if bia != nil {
|
||||
plugin = bia.Name()
|
||||
}
|
||||
if len(errMsg) > 0 {
|
||||
errMsg += ", "
|
||||
}
|
||||
return fmt.Sprintf("%splugin: %s", errMsg, plugin)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -286,6 +288,7 @@ func TestBackupOperationsReconcile(t *testing.T) {
|
||||
backupStore.On("PutBackupItemOperations", mock.Anything, mock.Anything).Return(nil)
|
||||
backupStore.On("PutBackupMetadata", mock.Anything, mock.Anything).Return(nil)
|
||||
for _, operation := range test.backupOperations {
|
||||
bia.On("Name").Return("test")
|
||||
bia.On("Progress", operation.Spec.OperationID, mock.Anything).
|
||||
Return(velero.OperationProgress{
|
||||
Completed: test.operationComplete,
|
||||
@@ -308,3 +311,40 @@ func TestBackupOperationsReconcile(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapErrMsg(t *testing.T) {
|
||||
bia2 := &biav2mocks.BackupItemAction{}
|
||||
bia2.On("Name").Return("test-bia")
|
||||
cases := []struct {
|
||||
name string
|
||||
inputErr string
|
||||
plugin v2.BackupItemAction
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
name: "empty error message",
|
||||
inputErr: "",
|
||||
plugin: bia2,
|
||||
expect: "plugin: test-bia",
|
||||
},
|
||||
{
|
||||
name: "nil bia",
|
||||
inputErr: "some error happened",
|
||||
plugin: nil,
|
||||
expect: "some error happened, plugin: unknown",
|
||||
},
|
||||
{
|
||||
name: "regular error and bia",
|
||||
inputErr: "some error happened",
|
||||
plugin: bia2,
|
||||
expect: "some error happened, plugin: test-bia",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := wrapErrMsg(test.inputErr, test.plugin)
|
||||
assert.Equal(t, test.expect, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
|
||||
@@ -142,6 +143,16 @@ func (b *backupSyncReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
||||
log = log.WithField("backup", backupName)
|
||||
log.Info("Attempting to sync backup into cluster")
|
||||
|
||||
exist, err := backupStore.BackupExists(location.Spec.ObjectStorage.Bucket, backupName)
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("Error checking backup exist from backup store")
|
||||
continue
|
||||
}
|
||||
if !exist {
|
||||
log.Debugf("backup %s doesn't exist in backup store, skip", backupName)
|
||||
continue
|
||||
}
|
||||
|
||||
backup, err := backupStore.GetBackupMetadata(backupName)
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("Error getting backup metadata from backup store")
|
||||
@@ -171,6 +182,9 @@ func (b *backupSyncReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
||||
}
|
||||
backup.Labels[velerov1api.StorageLocationLabel] = label.GetValidName(backup.Spec.StorageLocation)
|
||||
|
||||
//check for the ownership references. If they do not exist, remove them.
|
||||
backup.ObjectMeta.OwnerReferences = b.filterBackupOwnerReferences(ctx, backup, log)
|
||||
|
||||
// attempt to create backup custom resource via API
|
||||
err = b.client.Create(ctx, backup, &client.CreateOptions{})
|
||||
switch {
|
||||
@@ -286,6 +300,35 @@ func (b *backupSyncReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (b *backupSyncReconciler) filterBackupOwnerReferences(ctx context.Context, backup *velerov1api.Backup, log logrus.FieldLogger) []metav1.OwnerReference {
|
||||
listedReferences := backup.ObjectMeta.OwnerReferences
|
||||
foundReferences := make([]metav1.OwnerReference, 0)
|
||||
for _, v := range listedReferences {
|
||||
switch v.Kind {
|
||||
case "Schedule":
|
||||
schedule := new(velerov1api.Schedule)
|
||||
err := b.client.Get(ctx, types.NamespacedName{
|
||||
Name: v.Name,
|
||||
Namespace: backup.Namespace,
|
||||
}, schedule)
|
||||
switch {
|
||||
case err != nil && apierrors.IsNotFound(err):
|
||||
log.Warnf("Removing missing schedule ownership reference %s/%s from backup", backup.Namespace, v.Name)
|
||||
continue
|
||||
case schedule.UID != v.UID:
|
||||
log.Warnf("Removing schedule ownership reference with mismatched UIDs. Expected %s, got %s", v.UID, schedule.UID)
|
||||
continue
|
||||
case err != nil && !apierrors.IsNotFound(err):
|
||||
log.WithError(errors.WithStack(err)).Error("Error finding schedule ownership reference, keeping schedule on backup")
|
||||
}
|
||||
default:
|
||||
log.Warnf("Unable to check ownership reference for unknown kind, %s", v.Kind)
|
||||
}
|
||||
foundReferences = append(foundReferences, v)
|
||||
}
|
||||
return foundReferences
|
||||
}
|
||||
|
||||
// SetupWithManager is used to setup controller and its watching sources.
|
||||
func (b *backupSyncReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
backupSyncSource := kube.NewPeriodicalEnqueueSource(
|
||||
|
||||
@@ -429,6 +429,7 @@ var _ = Describe("Backup Sync Reconciler", func() {
|
||||
backupNames = append(backupNames, backup.backup.Name)
|
||||
backupStore.On("GetBackupMetadata", backup.backup.Name).Return(backup.backup, nil)
|
||||
backupStore.On("GetPodVolumeBackups", backup.backup.Name).Return(backup.podVolumeBackups, nil)
|
||||
backupStore.On("BackupExists", "bucket-1", backup.backup.Name).Return(true, nil)
|
||||
}
|
||||
backupStore.On("ListBackups").Return(backupNames, nil)
|
||||
}
|
||||
@@ -724,4 +725,174 @@ var _ = Describe("Backup Sync Reconciler", func() {
|
||||
Expect(testObjList).To(BeEquivalentTo(locationList))
|
||||
|
||||
})
|
||||
|
||||
When("testing validateOwnerReferences", func() {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
backup *velerov1api.Backup
|
||||
toCreate []ctrlClient.Object
|
||||
expectedReferences []metav1.OwnerReference
|
||||
}{
|
||||
{
|
||||
name: "handles empty owner references",
|
||||
backup: &velerov1api.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: []metav1.OwnerReference{},
|
||||
},
|
||||
},
|
||||
expectedReferences: []metav1.OwnerReference{},
|
||||
},
|
||||
{
|
||||
name: "handles missing schedule",
|
||||
backup: &velerov1api.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "Schedule",
|
||||
Name: "some name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedReferences: []metav1.OwnerReference{},
|
||||
},
|
||||
{
|
||||
name: "handles existing reference",
|
||||
backup: &velerov1api.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "Schedule",
|
||||
Name: "existing-schedule",
|
||||
},
|
||||
},
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
},
|
||||
toCreate: []ctrlClient.Object{
|
||||
&velerov1api.Schedule{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "existing-schedule",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedReferences: []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "Schedule",
|
||||
Name: "existing-schedule",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "handles existing mismatched UID",
|
||||
backup: &velerov1api.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "Schedule",
|
||||
Name: "existing-schedule",
|
||||
UID: "backup-UID",
|
||||
},
|
||||
},
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
},
|
||||
toCreate: []ctrlClient.Object{
|
||||
&velerov1api.Schedule{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "existing-schedule",
|
||||
Namespace: "test-namespace",
|
||||
UID: "schedule-UID",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedReferences: []metav1.OwnerReference{},
|
||||
},
|
||||
{
|
||||
name: "handles multiple references",
|
||||
backup: &velerov1api.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "Schedule",
|
||||
Name: "existing-schedule",
|
||||
UID: "1",
|
||||
},
|
||||
{
|
||||
Kind: "Schedule",
|
||||
Name: "missing-schedule",
|
||||
UID: "2",
|
||||
},
|
||||
{
|
||||
Kind: "Schedule",
|
||||
Name: "mismatched-uid-schedule",
|
||||
UID: "3",
|
||||
},
|
||||
{
|
||||
Kind: "Schedule",
|
||||
Name: "another-existing-schedule",
|
||||
UID: "4",
|
||||
},
|
||||
},
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
},
|
||||
toCreate: []ctrlClient.Object{
|
||||
&velerov1api.Schedule{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "existing-schedule",
|
||||
Namespace: "test-namespace",
|
||||
UID: "1",
|
||||
},
|
||||
},
|
||||
&velerov1api.Schedule{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mismatched-uid-schedule",
|
||||
Namespace: "test-namespace",
|
||||
UID: "not-3",
|
||||
},
|
||||
},
|
||||
&velerov1api.Schedule{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "another-existing-schedule",
|
||||
Namespace: "test-namespace",
|
||||
UID: "4",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedReferences: []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "Schedule",
|
||||
Name: "existing-schedule",
|
||||
UID: "1",
|
||||
},
|
||||
{
|
||||
Kind: "Schedule",
|
||||
Name: "another-existing-schedule",
|
||||
UID: "4",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
It(test.name, func() {
|
||||
logger := velerotest.NewLogger()
|
||||
b := backupSyncReconciler{
|
||||
client: ctrlfake.NewClientBuilder().Build(),
|
||||
}
|
||||
|
||||
//create all required schedules as needed.
|
||||
for _, creatable := range test.toCreate {
|
||||
err := b.client.Create(context.Background(), creatable)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
}
|
||||
|
||||
references := b.filterBackupOwnerReferences(context.Background(), test.backup, logger)
|
||||
Expect(references).To(BeEquivalentTo(test.expectedReferences))
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -69,7 +69,7 @@ type DataDownloadReconciler struct {
|
||||
metrics *metrics.ServerMetrics
|
||||
}
|
||||
|
||||
func NewDataDownloadReconciler(client client.Client, kubeClient kubernetes.Interface,
|
||||
func NewDataDownloadReconciler(client client.Client, kubeClient kubernetes.Interface, dataPathMgr *datapath.Manager,
|
||||
repoEnsurer *repository.Ensurer, credentialGetter *credentials.CredentialGetter, nodeName string, preparingTimeout time.Duration, logger logrus.FieldLogger, metrics *metrics.ServerMetrics) *DataDownloadReconciler {
|
||||
return &DataDownloadReconciler{
|
||||
client: client,
|
||||
@@ -81,7 +81,7 @@ func NewDataDownloadReconciler(client client.Client, kubeClient kubernetes.Inter
|
||||
nodeName: nodeName,
|
||||
repositoryEnsurer: repoEnsurer,
|
||||
restoreExposer: exposer.NewGenericRestoreExposer(kubeClient, logger),
|
||||
dataPathMgr: datapath.NewManager(1),
|
||||
dataPathMgr: dataPathMgr,
|
||||
preparingTimeout: preparingTimeout,
|
||||
metrics: metrics,
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user