Convert manifests + BSL api client to kubebuilder (#2561)

* kubebuilder init - minimalist version

Signed-off-by: Carlisia <carlisia@vmware.com>

* Add back main.go, apparently kb needs it

Signed-off-by: Carlisia <carlisia@vmware.com>

* Tweak makefile to accomodate kubebuilder expectations

Signed-off-by: Carlisia <carlisia@vmware.com>

* Port BSL to kubebuilder api client

Signed-off-by: Carlisia <carlisia@vmware.com>

* s/cache/client bc client fetches from cache
And other naming improvements

Signed-off-by: Carlisia <carlisia@vmware.com>

* So, .GetAPIReader is how we bypass the cache
In this case, the cache hasn't started yet

Signed-off-by: Carlisia <carlisia@vmware.com>

* Oh that's what this code was for... adding back

We still need to embed the CRDs as binary data in the Velero binary to
access the generated CRDs at runtime.

Signed-off-by: Carlisia <carlisia@vmware.com>

* Tie in CRD/code generation w/ existing scripts

Signed-off-by: Carlisia <carlisia@vmware.com>

* Mostly result of running update-fmt, updated file formatting

Signed-off-by: Carlisia <carlisia@vmware.com>

* Just a copyright fix

Signed-off-by: Carlisia <carlisia@vmware.com>

* All the test fixes

Signed-off-by: Carlisia <carlisia@vmware.com>

* Add changelog + some cleanup

Signed-off-by: Carlisia <carlisia@vmware.com>

* Update backup manifest

Signed-off-by: Carlisia <carlisia@vmware.com>

* Remove unneeded auto-generated files

Signed-off-by: Carlisia <carlisia@vmware.com>

* Keep everything in the same (existing) package

Signed-off-by: Carlisia <carlisia@vmware.com>

* Fix/clean scripts, generated code, and calls

Deleting the entire `generated` directory and running `make update`
works. Modifying an api and running `make verify` works as expected.

Signed-off-by: Carlisia <carlisia@vmware.com>

* Clean up schema and client calls + code reviews

Signed-off-by: Carlisia <carlisia@vmware.com>

* Move all code gen to inside builder container

Signed-off-by: Carlisia <carlisia@vmware.com>

* Address code review

Signed-off-by: Carlisia <carlisia@vmware.com>

* Fix imports/aliases

Signed-off-by: Carlisia <carlisia@vmware.com>

* More code reviews

Signed-off-by: Carlisia <carlisia@vmware.com>

* Add waitforcachesync

Signed-off-by: Carlisia <carlisia@vmware.com>

* Have manager register ALL controllers

This will allow for proper cache management.

Signed-off-by: Carlisia <carlisia@vmware.com>

* Status subresource is now enabled; cleanup

Signed-off-by: Carlisia <carlisia@vmware.com>

* More code reviews

Signed-off-by: Carlisia <carlisia@vmware.com>

* Clean up

Signed-off-by: Carlisia <carlisia@vmware.com>

* Manager registers ALL controllers for restic too

Signed-off-by: Carlisia <carlisia@vmware.com>

* More code reviews

Signed-off-by: Carlisia <carlisia@vmware.com>

* Add deprecation warning/todo

Signed-off-by: Carlisia <carlisia@vmware.com>

* Add documentation

Signed-off-by: Carlisia <carlisia@vmware.com>

* Add helpful comments

Signed-off-by: Carlisia <carlisia@vmware.com>

* Address code review

Signed-off-by: Carlisia <carlisia@vmware.com>

* More idiomatic Runnable

Signed-off-by: Carlisia <carlisia@vmware.com>

* Clean up imports

Signed-off-by: Carlisia <carlisia@vmware.com>
pull/2676/head
Carlisia Campos 2020-06-24 09:55:18 -07:00 committed by GitHub
parent 6e86a83cf3
commit 4048c020a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1946 additions and 732 deletions

7
PROJECT Normal file
View File

@ -0,0 +1,7 @@
domain: io
repo: github.com/vmware-tanzu/velero
resources:
- group: velero
kind: BackupStorageLocation
version: v1
version: "2"

View File

@ -0,0 +1 @@
Convert manifests + BSL api client to kubebuilder

View File

@ -0,0 +1,423 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
creationTimestamp: null
name: backups.velero.io
spec:
group: velero.io
names:
kind: Backup
listKind: BackupList
plural: backups
singular: backup
preserveUnknownFields: false
scope: Namespaced
validation:
openAPIV3Schema:
description: Backup is a Velero resource that respresents the capture of Kubernetes
cluster state at a point in time (API objects and associated volume state).
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: BackupSpec defines the specification for a Velero backup.
properties:
defaultVolumesToRestic:
description: DefaultVolumesToRestic specifies whether restic should
be used to take a backup of all pod volumes by default.
type: boolean
excludedNamespaces:
description: ExcludedNamespaces contains a list of namespaces that are
not included in the backup.
items:
type: string
nullable: true
type: array
excludedResources:
description: ExcludedResources is a slice of resource names that are
not included in the backup.
items:
type: string
nullable: true
type: array
hooks:
description: Hooks represent custom behaviors that should be executed
at different phases of the backup.
properties:
resources:
description: Resources are hooks that should be executed when backing
up individual instances of a resource.
items:
description: BackupResourceHookSpec defines one or more BackupResourceHooks
that should be executed based on the rules defined for namespaces,
resources, and label selector.
properties:
excludedNamespaces:
description: ExcludedNamespaces specifies the namespaces to
which this hook spec does not apply.
items:
type: string
nullable: true
type: array
excludedResources:
description: ExcludedResources specifies the resources to
which this hook spec does not apply.
items:
type: string
nullable: true
type: array
includedNamespaces:
description: IncludedNamespaces specifies the namespaces to
which this hook spec applies. If empty, it applies to all
namespaces.
items:
type: string
nullable: true
type: array
includedResources:
description: IncludedResources specifies the resources to
which this hook spec applies. If empty, it applies to all
resources.
items:
type: string
nullable: true
type: array
labelSelector:
description: LabelSelector, if specified, filters the resources
to which this hook spec applies.
nullable: true
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In, NotIn,
Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values array
must be non-empty. If the operator is Exists or
DoesNotExist, the values array must be empty.
This array is replaced during a strategic merge
patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field is
"key", the operator is "In", and the values array contains
only "value". The requirements are ANDed.
type: object
type: object
name:
description: Name is the name of this hook.
type: string
post:
description: PostHooks is a list of BackupResourceHooks to
execute after storing the item in the backup. These are
executed after all "additional items" from item actions
are processed.
items:
description: BackupResourceHook defines a hook for a resource.
properties:
exec:
description: Exec defines an exec hook.
properties:
command:
description: Command is the command and arguments
to execute.
items:
type: string
minItems: 1
type: array
container:
description: Container is the container in the pod
where the command should be executed. If not specified,
the pod's first container is used.
type: string
onError:
description: OnError specifies how Velero should
behave if it encounters an error executing this
hook.
enum:
- Continue
- Fail
type: string
timeout:
description: Timeout defines the maximum amount
of time Velero should wait for the hook to complete
before considering the execution a failure.
type: string
required:
- command
type: object
required:
- exec
type: object
type: array
pre:
description: PreHooks is a list of BackupResourceHooks to
execute prior to storing the item in the backup. These are
executed before any "additional items" from item actions
are processed.
items:
description: BackupResourceHook defines a hook for a resource.
properties:
exec:
description: Exec defines an exec hook.
properties:
command:
description: Command is the command and arguments
to execute.
items:
type: string
minItems: 1
type: array
container:
description: Container is the container in the pod
where the command should be executed. If not specified,
the pod's first container is used.
type: string
onError:
description: OnError specifies how Velero should
behave if it encounters an error executing this
hook.
enum:
- Continue
- Fail
type: string
timeout:
description: Timeout defines the maximum amount
of time Velero should wait for the hook to complete
before considering the execution a failure.
type: string
required:
- command
type: object
required:
- exec
type: object
type: array
required:
- name
type: object
nullable: true
type: array
type: object
includeClusterResources:
description: IncludeClusterResources specifies whether cluster-scoped
resources should be included for consideration in the backup.
nullable: true
type: boolean
includedNamespaces:
description: IncludedNamespaces is a slice of namespace names to include
objects from. If empty, all namespaces are included.
items:
type: string
nullable: true
type: array
includedResources:
description: IncludedResources is a slice of resource names to include
in the backup. If empty, all resources are included.
items:
type: string
nullable: true
type: array
labelSelector:
description: LabelSelector is a metav1.LabelSelector to filter with
when adding individual objects to the backup. If empty or nil, all
objects are included. Optional.
nullable: true
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements.
The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains
values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies
to.
type: string
operator:
description: operator represents a key's relationship to a
set of values. Valid operators are In, NotIn, Exists and
DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator
is In or NotIn, the values array must be non-empty. If the
operator is Exists or DoesNotExist, the values array must
be empty. This array is replaced during a strategic merge
patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single
{key,value} in the matchLabels map is equivalent to an element
of matchExpressions, whose key field is "key", the operator is
"In", and the values array contains only "value". The requirements
are ANDed.
type: object
type: object
snapshotVolumes:
description: SnapshotVolumes specifies whether to take cloud snapshots
of any PV's referenced in the set of objects included in the Backup.
nullable: true
type: boolean
storageLocation:
description: StorageLocation is a string containing the name of a BackupStorageLocation
where the backup should be stored.
type: string
ttl:
description: TTL is a time.Duration-parseable string describing how
long the Backup should be retained for.
type: string
volumeSnapshotLocations:
description: VolumeSnapshotLocations is a list containing names of VolumeSnapshotLocations
associated with this backup.
items:
type: string
type: array
type: object
status:
description: BackupStatus captures the current status of a Velero backup.
properties:
completionTimestamp:
description: CompletionTimestamp records the time a backup was completed.
Completion time is recorded even on failed backups. Completion time
is recorded before uploading the backup object. The server's time
is used for CompletionTimestamps
format: date-time
nullable: true
type: string
errors:
description: Errors is a count of all error messages that were generated
during execution of the backup. The actual errors are in the backup's
log file in object storage.
type: integer
expiration:
description: Expiration is when this Backup is eligible for garbage-collection.
format: date-time
nullable: true
type: string
formatVersion:
description: FormatVersion is the backup format version, including major,
minor, and patch version.
type: string
phase:
description: Phase is the current state of the Backup.
enum:
- New
- FailedValidation
- InProgress
- Completed
- PartiallyFailed
- Failed
- Deleting
type: string
progress:
description: Progress contains information about the backup's execution
progress. Note that this information is best-effort only -- if Velero
fails to update it during a backup for any reason, it may be inaccurate/stale.
nullable: true
properties:
itemsBackedUp:
description: ItemsBackedUp is the number of items that have actually
been written to the backup tarball so far.
type: integer
totalItems:
description: TotalItems is the total number of items to be backed
up. This number may change throughout the execution of the backup
due to plugins that return additional related items to back up,
the velero.io/exclude-from-backup label, and various other filters
that happen as items are processed.
type: integer
type: object
startTimestamp:
description: StartTimestamp records the time a backup was started. Separate
from CreationTimestamp, since that value changes on restores. The
server's time is used for StartTimestamps
format: date-time
nullable: true
type: string
validationErrors:
description: ValidationErrors is a slice of all validation errors (if
applicable).
items:
type: string
nullable: true
type: array
version:
description: 'Version is the backup format major version. Deprecated:
Please see FormatVersion'
type: integer
volumeSnapshotsAttempted:
description: VolumeSnapshotsAttempted is the total number of attempted
volume snapshots for this backup.
type: integer
volumeSnapshotsCompleted:
description: VolumeSnapshotsCompleted is the total number of successfully
completed volume snapshots for this backup.
type: integer
warnings:
description: Warnings is a count of all warning messages that were generated
during execution of the backup. The actual warnings are in the backup's
log file in object storage.
type: integer
type: object
type: object
version: v1
versions:
- name: v1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -8,6 +8,14 @@ metadata:
creationTimestamp: null
name: backupstoragelocations.velero.io
spec:
additionalPrinterColumns:
- JSONPath: .status.phase
description: Backup Storage Location status such as Available/Unavailable
name: Phase
type: string
- JSONPath: .metadata.creationTimestamp
name: Age
type: date
group: velero.io
names:
kind: BackupStorageLocation
@ -16,10 +24,12 @@ spec:
singular: backupstoragelocation
preserveUnknownFields: false
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
description: BackupStorageLocation is a location where Velero stores backup
objects.
objects
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -34,8 +44,8 @@ spec:
metadata:
type: object
spec:
description: BackupStorageLocationSpec defines the specification for a Velero
BackupStorageLocation.
description: BackupStorageLocationSpec defines the desired state of a Velero
BackupStorageLocation
properties:
accessMode:
description: AccessMode defines the permissions for the backup storage
@ -81,8 +91,7 @@ spec:
- provider
type: object
status:
description: BackupStorageLocationStatus describes the current status of
a Velero BackupStorageLocation.
description: BackupStorageLocationStatus defines the observed state of BackupStorageLocation
properties:
accessMode:
description: "AccessMode is an unused field. \n Deprecated: there is

View File

@ -0,0 +1,379 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
creationTimestamp: null
name: schedules.velero.io
spec:
group: velero.io
names:
kind: Schedule
listKind: ScheduleList
plural: schedules
singular: schedule
preserveUnknownFields: false
scope: Namespaced
validation:
openAPIV3Schema:
description: Schedule is a Velero resource that represents a pre-scheduled or
periodic Backup that should be run.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ScheduleSpec defines the specification for a Velero schedule
properties:
schedule:
description: Schedule is a Cron expression defining when to run the
Backup.
type: string
template:
description: Template is the definition of the Backup to be run on the
provided schedule
properties:
defaultVolumesToRestic:
description: DefaultVolumesToRestic specifies whether restic should
be used to take a backup of all pod volumes by default.
type: boolean
excludedNamespaces:
description: ExcludedNamespaces contains a list of namespaces that
are not included in the backup.
items:
type: string
nullable: true
type: array
excludedResources:
description: ExcludedResources is a slice of resource names that
are not included in the backup.
items:
type: string
nullable: true
type: array
hooks:
description: Hooks represent custom behaviors that should be executed
at different phases of the backup.
properties:
resources:
description: Resources are hooks that should be executed when
backing up individual instances of a resource.
items:
description: BackupResourceHookSpec defines one or more BackupResourceHooks
that should be executed based on the rules defined for namespaces,
resources, and label selector.
properties:
excludedNamespaces:
description: ExcludedNamespaces specifies the namespaces
to which this hook spec does not apply.
items:
type: string
nullable: true
type: array
excludedResources:
description: ExcludedResources specifies the resources
to which this hook spec does not apply.
items:
type: string
nullable: true
type: array
includedNamespaces:
description: IncludedNamespaces specifies the namespaces
to which this hook spec applies. If empty, it applies
to all namespaces.
items:
type: string
nullable: true
type: array
includedResources:
description: IncludedResources specifies the resources
to which this hook spec applies. If empty, it applies
to all resources.
items:
type: string
nullable: true
type: array
labelSelector:
description: LabelSelector, if specified, filters the
resources to which this hook spec applies.
nullable: true
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In,
NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values
array must be non-empty. If the operator is
Exists or DoesNotExist, the values array must
be empty. This array is replaced during a
strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field
is "key", the operator is "In", and the values array
contains only "value". The requirements are ANDed.
type: object
type: object
name:
description: Name is the name of this hook.
type: string
post:
description: PostHooks is a list of BackupResourceHooks
to execute after storing the item in the backup. These
are executed after all "additional items" from item
actions are processed.
items:
description: BackupResourceHook defines a hook for a
resource.
properties:
exec:
description: Exec defines an exec hook.
properties:
command:
description: Command is the command and arguments
to execute.
items:
type: string
minItems: 1
type: array
container:
description: Container is the container in the
pod where the command should be executed.
If not specified, the pod's first container
is used.
type: string
onError:
description: OnError specifies how Velero should
behave if it encounters an error executing
this hook.
enum:
- Continue
- Fail
type: string
timeout:
description: Timeout defines the maximum amount
of time Velero should wait for the hook to
complete before considering the execution
a failure.
type: string
required:
- command
type: object
required:
- exec
type: object
type: array
pre:
description: PreHooks is a list of BackupResourceHooks
to execute prior to storing the item in the backup.
These are executed before any "additional items" from
item actions are processed.
items:
description: BackupResourceHook defines a hook for a
resource.
properties:
exec:
description: Exec defines an exec hook.
properties:
command:
description: Command is the command and arguments
to execute.
items:
type: string
minItems: 1
type: array
container:
description: Container is the container in the
pod where the command should be executed.
If not specified, the pod's first container
is used.
type: string
onError:
description: OnError specifies how Velero should
behave if it encounters an error executing
this hook.
enum:
- Continue
- Fail
type: string
timeout:
description: Timeout defines the maximum amount
of time Velero should wait for the hook to
complete before considering the execution
a failure.
type: string
required:
- command
type: object
required:
- exec
type: object
type: array
required:
- name
type: object
nullable: true
type: array
type: object
includeClusterResources:
description: IncludeClusterResources specifies whether cluster-scoped
resources should be included for consideration in the backup.
nullable: true
type: boolean
includedNamespaces:
description: IncludedNamespaces is a slice of namespace names to
include objects from. If empty, all namespaces are included.
items:
type: string
nullable: true
type: array
includedResources:
description: IncludedResources is a slice of resource names to include
in the backup. If empty, all resources are included.
items:
type: string
nullable: true
type: array
labelSelector:
description: LabelSelector is a metav1.LabelSelector to filter with
when adding individual objects to the backup. If empty or nil,
all objects are included. Optional.
nullable: true
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements.
The requirements are ANDed.
items:
description: A label selector requirement is a selector that
contains values, a key, and an operator that relates the
key and values.
properties:
key:
description: key is the label key that the selector applies
to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In, NotIn, Exists
and DoesNotExist.
type: string
values:
description: values is an array of string values. If the
operator is In or NotIn, the values array must be non-empty.
If the operator is Exists or DoesNotExist, the values
array must be empty. This array is replaced during a
strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single
{key,value} in the matchLabels map is equivalent to an element
of matchExpressions, whose key field is "key", the operator
is "In", and the values array contains only "value". The requirements
are ANDed.
type: object
type: object
snapshotVolumes:
description: SnapshotVolumes specifies whether to take cloud snapshots
of any PV's referenced in the set of objects included in the Backup.
nullable: true
type: boolean
storageLocation:
description: StorageLocation is a string containing the name of
a BackupStorageLocation where the backup should be stored.
type: string
ttl:
description: TTL is a time.Duration-parseable string describing
how long the Backup should be retained for.
type: string
volumeSnapshotLocations:
description: VolumeSnapshotLocations is a list containing names
of VolumeSnapshotLocations associated with this backup.
items:
type: string
type: array
type: object
required:
- schedule
- template
type: object
status:
description: ScheduleStatus captures the current state of a Velero schedule
properties:
lastBackup:
description: LastBackup is the last time a Backup was run for this Schedule
schedule
format: date-time
nullable: true
type: string
phase:
description: Phase is the current phase of the Schedule
enum:
- New
- Enabled
- FailedValidation
type: string
validationErrors:
description: ValidationErrors is a slice of all validation errors (if
applicable)
items:
type: string
type: array
type: object
type: object
version: v1
versions:
- name: v1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

69
config/crd/crds/crds.go Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,16 @@
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
creationTimestamp: null
labels:
component: velero
name: default
namespace: velero
spec:
config:
region: minio
s3ForcePathStyle: "true"
s3Url: http://minio.velero.svc:9000
objectStorage:
bucket: velero
provider: aws

3
go.mod
View File

@ -30,7 +30,7 @@ require (
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
google.golang.org/grpc v1.26.0
k8s.io/api v0.17.4
k8s.io/apiextensions-apiserver v0.17.4
@ -39,4 +39,5 @@ require (
k8s.io/client-go v0.17.4
k8s.io/klog v1.0.0
k8s.io/utils v0.0.0-20191218082557-f07c713de883 // indirect
sigs.k8s.io/controller-runtime v0.5.2
)

39
go.sum
View File

@ -58,7 +58,9 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@ -103,6 +105,7 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docker/spdystream v0.0.0-20170912183627-bc6354cbbc29 h1:llBx5m8Gk0lrAaiLud2wktkX/e8haX7Ru0oVfQqtZQ4=
github.com/docker/spdystream v0.0.0-20170912183627-bc6354cbbc29/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M=
@ -125,7 +128,10 @@ github.com/go-ini/ini v1.28.2 h1:drmmYv7psRpoGZkPtPKKTB+ZFSnvmwCMfNj5o1nLh2Y=
github.com/go-ini/ini v1.28.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
@ -212,6 +218,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -234,6 +242,7 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@ -306,9 +315,13 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.2 h1:uqH7bpe+ERSiDa34FDOF7RikN6RzXgduUF8yarlZp94=
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@ -383,8 +396,11 @@ go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -396,6 +412,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -439,6 +456,8 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@ -468,8 +487,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220220014-0732a990476f h1:72l8qCJ1nGxMGH26QVBVIxKd/D34cfGt0OvrPtpemyY=
golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -502,7 +524,10 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
@ -533,10 +558,12 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
@ -552,6 +579,7 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
@ -561,23 +589,30 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
k8s.io/api v0.17.4 h1:HbwOhDapkguO8lTAE8OX3hdF2qp8GtpC9CW/MQATXXo=
k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA=
k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
k8s.io/apiextensions-apiserver v0.17.4 h1:ZKFnw3cJrGZ/9s6y+DerTF4FL+dmK0a04A++7JkmMho=
k8s.io/apiextensions-apiserver v0.17.4/go.mod h1:rCbbbaFS/s3Qau3/1HbPlHblrWpFivoaLYccCffvQGI=
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.17.1-beta.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.17.4 h1:UzM+38cPUJnzqSQ+E1PY4YxMHIzQyCg29LOoGfo79Zw=
k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I=
k8s.io/cli-runtime v0.17.4 h1:ZIJdxpBEszZqUhydrCoiI5rLXS2J/1AF5xFok2QJ9bc=
k8s.io/cli-runtime v0.17.4/go.mod h1:IVW4zrKKx/8gBgNNkhiUIc7nZbVVNhc1+HcQh+PiNHc=
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI=
k8s.io/client-go v0.17.4 h1:VVdVbpTY70jiNHS1eiFkUt7ZIJX3txd29nDxxXH4en8=
k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc=
k8s.io/code-generator v0.0.0-20191121015212-c4c8f8345c7e/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/code-generator v0.17.4/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=
k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=
k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=
k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
@ -597,6 +632,8 @@ modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/controller-runtime v0.5.2 h1:pyXbUfoTo+HA3jeIfr0vgi+1WtmNh0CwlcnQGLXwsSw=
sigs.k8s.io/controller-runtime v0.5.2/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// This code embeds the CRD manifests in pkg/generated/crds/manifests in
// pkg/generated/crds/crds.go.
// This code embeds the CRD manifests in config/crd/bases in
// config/crd/crds/crds.go.
package main
@ -30,7 +30,7 @@ import (
"text/template"
)
// This is relative to pkg/generated/crds
// This is relative to config/crd/crds
const goHeaderFile = "../../../hack/boilerplate.go.txt"
const tpl = `{{.GoHeader}}
@ -96,14 +96,14 @@ func main() {
GoHeader: string(headerBytes),
}
// This is relative to pkg/generated/crds
manifests, err := ioutil.ReadDir("manifests")
// This is relative to config/crd/crds
manifests, err := ioutil.ReadDir("../bases")
if err != nil {
log.Fatalln(err)
}
for _, crd := range manifests {
file, err := os.Open("manifests/" + crd.Name())
file, err := os.Open("../bases/" + crd.Name())
if err != nil {
log.Fatalln(err)
}
@ -120,7 +120,7 @@ func main() {
data.RawCRDs = append(data.RawCRDs, fmt.Sprintf("%q", buf.Bytes()))
}
t, err := template.New("crds").Parse(tpl)
t, err := template.New("crd").Parse(tpl)
if err != nil {
log.Fatalln(err)
}

View File

@ -44,9 +44,12 @@ ${GOPATH}/src/k8s.io/code-generator/generate-groups.sh \
--output-base ../../.. \
$@
# Generate manifests e.g. CRD, RBAC etc.
controller-gen \
crd:crdVersions=v1beta1,preserveUnknownFields=false \
output:dir=./pkg/generated/crds/manifests \
paths=./pkg/apis/velero/v1/...
crd:crdVersions=v1beta1,preserveUnknownFields=false,trivialVersions=true \
rbac:roleName=manager-role \
paths=./pkg/apis/velero/v1/... \
paths=./pkg/controller/... \
output:crd:artifacts:config=config/crd/bases
go generate ./pkg/generated/crds
go generate ./config/crd/crds

View File

@ -19,11 +19,11 @@ HACK_DIR=$(dirname "${BASH_SOURCE}")
${HACK_DIR}/update-generated-crd-code.sh --verify-only
# ensure no changes to generated CRDs
if ! git diff --exit-code pkg/generated/crds/crds.go >/dev/null; then
if ! git diff --exit-code config/crd/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 pkg/generated/crds
git checkout config/crd/bases
echo "CRD verification - failed! Generated CRDs are out-of-date, please run 'make update'."
exit 1
fi
fi

View File

@ -0,0 +1,54 @@
/*
Copyright 2020 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.
*/
// TODO(2.0) After converting all controllers to runttime-controller,
// the functions in this file will no longer be needed and should be removed.
package managercontroller
import (
"context"
"sigs.k8s.io/controller-runtime/pkg/manager"
"github.com/vmware-tanzu/velero/pkg/controller"
)
// Runnable will turn a "regular" runnable component (such as a controller)
// into a controller-runtime Runnable
func Runnable(p controller.Interface, numWorkers int) manager.Runnable {
f := func(stop <-chan struct{}) error {
// Create a cancel context for handling the stop signal.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// If a signal is received on the stop channel, cancel the
// context. This will propagate the cancel into the p.Run
// function below.
go func() {
select {
case <-stop:
cancel()
case <-ctx.Done():
}
}()
// This is a blocking call that either completes
// or is cancellable on receiving a stop signal.
return p.Run(ctx, numWorkers)
}
return manager.RunnableFunc(f)
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2018 the Velero contributors.
Copyright 2017, 2020 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.
@ -21,56 +21,7 @@ import (
"k8s.io/apimachinery/pkg/types"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// BackupStorageLocation is a location where Velero stores backup objects.
type BackupStorageLocation struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata,omitempty"`
// +optional
Spec BackupStorageLocationSpec `json:"spec,omitempty"`
// +optional
Status BackupStorageLocationStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// BackupStorageLocationList is a list of BackupStorageLocations.
type BackupStorageLocationList struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ListMeta `json:"metadata,omitempty"`
Items []BackupStorageLocation `json:"items"`
}
// StorageType represents the type of storage that a backup location uses.
// ObjectStorage must be non-nil, since it is currently the only supported StorageType.
type StorageType struct {
ObjectStorage *ObjectStorageLocation `json:"objectStorage"`
}
// ObjectStorageLocation specifies the settings necessary to connect to a provider's object storage.
type ObjectStorageLocation struct {
// Bucket is the bucket to use for object storage.
Bucket string `json:"bucket"`
// Prefix is the path inside a bucket to use for Velero storage. Optional.
// +optional
Prefix string `json:"prefix,omitempty"`
// CACert defines a CA bundle to use when verifying TLS connections to the provider.
// +optional
CACert []byte `json:"caCert,omitempty"`
}
// BackupStorageLocationSpec defines the specification for a Velero BackupStorageLocation.
// BackupStorageLocationSpec defines the desired state of a Velero BackupStorageLocation
type BackupStorageLocationSpec struct {
// Provider is the provider of the backup storage.
Provider string `json:"provider"`
@ -91,34 +42,7 @@ type BackupStorageLocationSpec struct {
BackupSyncPeriod *metav1.Duration `json:"backupSyncPeriod,omitempty"`
}
// BackupStorageLocationPhase is the lifecyle phase of a Velero BackupStorageLocation.
// +kubebuilder:validation:Enum=Available;Unavailable
type BackupStorageLocationPhase string
const (
// BackupStorageLocationPhaseAvailable means the location is available to read and write from.
BackupStorageLocationPhaseAvailable BackupStorageLocationPhase = "Available"
// BackupStorageLocationPhaseUnavailable means the location is unavailable to read and write from.
BackupStorageLocationPhaseUnavailable BackupStorageLocationPhase = "Unavailable"
)
// BackupStorageLocationAccessMode represents the permissions for a BackupStorageLocation.
// +kubebuilder:validation:Enum=ReadOnly;ReadWrite
type BackupStorageLocationAccessMode string
const (
// BackupStorageLocationAccessModeReadOnly represents read-only access to a BackupStorageLocation.
BackupStorageLocationAccessModeReadOnly BackupStorageLocationAccessMode = "ReadOnly"
// BackupStorageLocationAccessModeReadWrite represents read and write access to a BackupStorageLocation.
BackupStorageLocationAccessModeReadWrite BackupStorageLocationAccessMode = "ReadWrite"
)
// TODO(2.0): remove the AccessMode field from BackupStorageLocationStatus.
// TODO(2.0): remove the LastSyncedRevision field from BackupStorageLocationStatus.
// BackupStorageLocationStatus describes the current status of a Velero BackupStorageLocation.
// BackupStorageLocationStatus defines the observed state of BackupStorageLocation
type BackupStorageLocationStatus struct {
// Phase is the current state of the BackupStorageLocation.
// +optional
@ -145,3 +69,82 @@ type BackupStorageLocationStatus struct {
// +optional
AccessMode BackupStorageLocationAccessMode `json:"accessMode,omitempty"`
}
// TODO(2.0) After converting all resources to use the runttime-controller client,
// the genclient and k8s:deepcopy markers will no longer be needed and should be removed.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
// +kubebuilder:object:generate=true
// +kubebuilder:storageversion
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="Backup Storage Location status such as Available/Unavailable"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// BackupStorageLocation is a location where Velero stores backup objects
type BackupStorageLocation struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec BackupStorageLocationSpec `json:"spec,omitempty"`
Status BackupStorageLocationStatus `json:"status,omitempty"`
}
// TODO(2.0) After converting all resources to use the runttime-controller client,
// the k8s:deepcopy marker will no longer be needed and should be removed.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
// BackupStorageLocationList contains a list of BackupStorageLocation
type BackupStorageLocationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []BackupStorageLocation `json:"items"`
}
// StorageType represents the type of storage that a backup location uses.
// ObjectStorage must be non-nil, since it is currently the only supported StorageType.
type StorageType struct {
ObjectStorage *ObjectStorageLocation `json:"objectStorage"`
}
// ObjectStorageLocation specifies the settings necessary to connect to a provider's object storage.
type ObjectStorageLocation struct {
// Bucket is the bucket to use for object storage.
Bucket string `json:"bucket"`
// Prefix is the path inside a bucket to use for Velero storage. Optional.
// +optional
Prefix string `json:"prefix,omitempty"`
// CACert defines a CA bundle to use when verifying TLS connections to the provider.
// +optional
CACert []byte `json:"caCert,omitempty"`
}
// BackupStorageLocationPhase is the lifecycle phase of a Velero BackupStorageLocation.
// +kubebuilder:validation:Enum=Available;Unavailable
type BackupStorageLocationPhase string
const (
// BackupStorageLocationPhaseAvailable means the location is available to read and write from.
BackupStorageLocationPhaseAvailable BackupStorageLocationPhase = "Available"
// BackupStorageLocationPhaseUnavailable means the location is unavailable to read and write from.
BackupStorageLocationPhaseUnavailable BackupStorageLocationPhase = "Unavailable"
)
// BackupStorageLocationAccessMode represents the permissions for a BackupStorageLocation.
// +kubebuilder:validation:Enum=ReadOnly;ReadWrite
type BackupStorageLocationAccessMode string
const (
// BackupStorageLocationAccessModeReadOnly represents read-only access to a BackupStorageLocation.
BackupStorageLocationAccessModeReadOnly BackupStorageLocationAccessMode = "ReadOnly"
// BackupStorageLocationAccessModeReadWrite represents read and write access to a BackupStorageLocation.
BackupStorageLocationAccessModeReadWrite BackupStorageLocationAccessMode = "ReadWrite"
)
// TODO(2.0): remove the AccessMode field from BackupStorageLocationStatus.
// TODO(2.0): remove the LastSyncedRevision field from BackupStorageLocationStatus.

View File

@ -0,0 +1,36 @@
/*
Copyright 2020 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 v1 contains API Schema definitions for the velero v1 API group
// +kubebuilder:object:generate=true
// +groupName=velero.io
package v1
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: "velero.io", Version: "v1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)

View File

@ -22,20 +22,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
var (
// SchemeBuilder collects the scheme builder functions for the Velero API
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme applies the SchemeBuilder functions to a specified scheme
AddToScheme = SchemeBuilder.AddToScheme
)
// GroupName is the group name for the Velero API
const GroupName = "velero.io"
// SchemeGroupVersion is the GroupVersion for the Velero API
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
// Resource gets a Velero GroupResource for a specified resource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()

View File

@ -19,13 +19,16 @@ package client
import (
"os"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/pkg/errors"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
)
@ -42,6 +45,9 @@ type Factory interface {
// DynamicClient returns a Kubernetes dynamic client. It uses the following priority to specify the cluster
// configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration.
DynamicClient() (dynamic.Interface, error)
// KubebuilderClient returns a Kubernetes dynamic client. It uses the following priority to specify the cluster
// configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration.
KubebuilderClient() (kbclient.Client, error)
// SetBasename changes the basename for an already-constructed client.
// This is useful for generating clients that require a different user-agent string below the root `velero`
// command, such as the server subcommand.
@ -81,7 +87,7 @@ func NewFactory(baseName string, config VeleroConfig) Factory {
// We didn't get the namespace via env var or config file, so use the default.
// Command line flags will override when BindFlags is called.
if f.namespace == "" {
f.namespace = v1.DefaultNamespace
f.namespace = velerov1api.DefaultNamespace
}
f.flags.StringVar(&f.kubeconfig, "kubeconfig", "", "Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration")
@ -137,6 +143,21 @@ func (f *factory) DynamicClient() (dynamic.Interface, error) {
return dynamicClient, nil
}
func (f *factory) KubebuilderClient() (kbclient.Client, error) {
clientConfig, err := f.ClientConfig()
if err != nil {
return nil, err
}
scheme := runtime.NewScheme()
velerov1api.AddToScheme(scheme)
kubebuilderClient, err := kbclient.New(clientConfig, kbclient.Options{
Scheme: scheme,
})
return kubebuilderClient, nil
}
func (f *factory) SetBasename(name string) {
f.baseName = name
}

View File

@ -17,9 +17,12 @@ limitations under the License.
package backup
import (
"context"
"fmt"
"time"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -146,13 +149,22 @@ func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Facto
return err
}
client, err := f.KubebuilderClient()
if err != nil {
return err
}
// Ensure that unless FromSchedule is set, args contains a backup name
if o.FromSchedule == "" && len(args) != 1 {
return fmt.Errorf("a backup name is required, unless you are creating based on a schedule")
}
if o.StorageLocation != "" {
if _, err := o.client.VeleroV1().BackupStorageLocations(f.Namespace()).Get(o.StorageLocation, metav1.GetOptions{}); err != nil {
location := &velerov1api.BackupStorageLocation{}
if err := client.Get(context.Background(), kbclient.ObjectKey{
Namespace: f.Namespace(),
Name: o.StorageLocation,
}, location); err != nil {
return err
}
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package backuplocation
import (
"context"
"fmt"
"strings"
"time"
@ -26,6 +27,8 @@ import (
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kbclient "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"
@ -146,12 +149,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().BackupStorageLocations(backupStorageLocation.Namespace).Create(backupStorageLocation); err != nil {
if err := client.Create(context.Background(), backupStorageLocation, &kbclient.CreateOptions{}); err != nil {
return errors.WithStack(err)
}

View File

@ -17,10 +17,14 @@ limitations under the License.
package backuplocation
import (
"context"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/output"
@ -36,19 +40,24 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command {
err := output.ValidateFlags(c)
cmd.CheckError(err)
veleroClient, err := f.Client()
client, err := f.KubebuilderClient()
cmd.CheckError(err)
var locations *api.BackupStorageLocationList
locations := new(velerov1api.BackupStorageLocationList)
if len(args) > 0 {
locations = new(api.BackupStorageLocationList)
location := &velerov1api.BackupStorageLocation{}
for _, name := range args {
location, err := veleroClient.VeleroV1().BackupStorageLocations(f.Namespace()).Get(name, metav1.GetOptions{})
err = client.Get(context.Background(), kbclient.ObjectKey{
Namespace: f.Namespace(),
Name: name,
}, location)
cmd.CheckError(err)
locations.Items = append(locations.Items, *location)
}
} else {
locations, err = veleroClient.VeleroV1().BackupStorageLocations(f.Namespace()).List(listOptions)
err := client.List(context.Background(), locations, &kbclient.ListOptions{
Namespace: f.Namespace(),
})
cmd.CheckError(err)
}

View File

@ -20,19 +20,24 @@ import (
"fmt"
"os"
"strings"
"sync"
"github.com/vmware-tanzu/velero/internal/util/managercontroller"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
kubeinformers "k8s.io/client-go/informers"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
ctrl "sigs.k8s.io/controller-runtime"
"github.com/vmware-tanzu/velero/pkg/buildinfo"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
@ -43,6 +48,13 @@ import (
"github.com/vmware-tanzu/velero/pkg/restic"
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
"github.com/vmware-tanzu/velero/pkg/util/logging"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/manager"
)
var (
scheme = runtime.NewScheme()
)
func NewServerCommand(f client.Factory) *cobra.Command {
@ -86,6 +98,7 @@ type resticServer struct {
ctx context.Context
cancelFunc context.CancelFunc
fileSystem filesystem.Interface
mgr manager.Manager
}
func newResticServer(logger logrus.FieldLogger, factory client.Factory) (*resticServer, error) {
@ -130,6 +143,20 @@ func newResticServer(logger logrus.FieldLogger, factory client.Factory) (*restic
ctx, cancelFunc := context.WithCancel(context.Background())
clientConfig, err := factory.ClientConfig()
if err != nil {
return nil, err
}
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
velerov1api.AddToScheme(scheme)
mgr, err := ctrl.NewManager(clientConfig, ctrl.Options{
Scheme: scheme,
})
if err != nil {
return nil, err
}
s := &resticServer{
kubeClient: kubeClient,
veleroClient: veleroClient,
@ -141,6 +168,7 @@ func newResticServer(logger logrus.FieldLogger, factory client.Factory) (*restic
ctx: ctx,
cancelFunc: cancelFunc,
fileSystem: filesystem.NewFileSystem(),
mgr: mgr,
}
if err := s.validatePodVolumesHostPath(); err != nil {
@ -155,8 +183,6 @@ func (s *resticServer) run() {
s.logger.Info("Starting controllers")
var wg sync.WaitGroup
backupController := controller.NewPodVolumeBackupController(
s.logger,
s.veleroInformerFactory.Velero().V1().PodVolumeBackups(),
@ -165,14 +191,9 @@ func (s *resticServer) run() {
s.secretInformer,
s.kubeInformerFactory.Core().V1().PersistentVolumeClaims(),
s.kubeInformerFactory.Core().V1().PersistentVolumes(),
s.veleroInformerFactory.Velero().V1().BackupStorageLocations(),
s.mgr.GetClient(),
os.Getenv("NODE_NAME"),
)
wg.Add(1)
go func() {
defer wg.Done()
backupController.Run(s.ctx, 1)
}()
restoreController := controller.NewPodVolumeRestoreController(
s.logger,
@ -182,26 +203,30 @@ func (s *resticServer) run() {
s.secretInformer,
s.kubeInformerFactory.Core().V1().PersistentVolumeClaims(),
s.kubeInformerFactory.Core().V1().PersistentVolumes(),
s.veleroInformerFactory.Velero().V1().BackupStorageLocations(),
s.mgr.GetClient(),
os.Getenv("NODE_NAME"),
)
wg.Add(1)
go func() {
defer wg.Done()
restoreController.Run(s.ctx, 1)
}()
go s.veleroInformerFactory.Start(s.ctx.Done())
go s.kubeInformerFactory.Start(s.ctx.Done())
go s.podInformer.Run(s.ctx.Done())
go s.secretInformer.Run(s.ctx.Done())
s.logger.Info("Controllers started successfully")
// TODO(2.0): presuming all controllers and resources are converted to runtime-controller
// by v2.0, the block from this line and including the `s.mgr.Start() will be
// deprecated, since the manager auto-starts all the caches. Until then, we need to start the
// cache for them manually.
<-s.ctx.Done()
// Adding the controllers to the manager will register them as a (runtime-controller) runnable,
// so the manager will ensure the cache is started and ready before all controller are started
s.mgr.Add(managercontroller.Runnable(backupController, 1))
s.mgr.Add(managercontroller.Runnable(restoreController, 1))
s.logger.Info("Waiting for all controllers to shut down gracefully")
wg.Wait()
s.logger.Info("Controllers starting...")
if err := s.mgr.Start(ctrl.SetupSignalHandler()); err != nil {
s.logger.Fatal("Problem starting manager", err)
}
}
// validatePodVolumesHostPath validates that the pod volumes path contains a

View File

@ -25,7 +25,6 @@ import (
"os"
"reflect"
"strings"
"sync"
"time"
"github.com/pkg/errors"
@ -34,6 +33,7 @@ import (
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
@ -56,6 +56,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/flag"
"github.com/vmware-tanzu/velero/pkg/cmd/util/signals"
"github.com/vmware-tanzu/velero/pkg/controller"
velerodiscovery "github.com/vmware-tanzu/velero/pkg/discovery"
"github.com/vmware-tanzu/velero/pkg/features"
@ -68,6 +69,14 @@ import (
"github.com/vmware-tanzu/velero/pkg/restic"
"github.com/vmware-tanzu/velero/pkg/restore"
"github.com/vmware-tanzu/velero/pkg/util/logging"
ctrl "sigs.k8s.io/controller-runtime"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
"github.com/vmware-tanzu/velero/internal/util/managercontroller"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
)
const (
@ -240,6 +249,7 @@ type server struct {
resticManager restic.RepositoryManager
metrics *metrics.ServerMetrics
config serverConfig
mgr manager.Manager
}
func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*server, error) {
@ -294,6 +304,16 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
}
}
scheme := runtime.NewScheme()
velerov1api.AddToScheme(scheme)
mgr, err := ctrl.NewManager(clientConfig, ctrl.Options{
Scheme: scheme,
})
if err != nil {
cancelFunc()
return nil, err
}
s := &server{
namespace: f.Namespace(),
metricsAddress: config.metricsAddress,
@ -311,6 +331,7 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
logLevel: logger.Level,
pluginRegistry: pluginRegistry,
config: config,
mgr: mgr,
}
return s, nil
@ -342,7 +363,13 @@ func (s *server) run() error {
return err
}
if _, err := s.veleroClient.VeleroV1().BackupStorageLocations(s.namespace).Get(s.config.defaultBackupLocation, metav1.GetOptions{}); err != nil {
// Fetching from the server directly since at this point
// the cache has not yet started
bsl := &velerov1api.BackupStorageLocation{}
if err := s.mgr.GetAPIReader().Get(context.Background(), kbclient.ObjectKey{
Namespace: s.namespace,
Name: s.config.defaultBackupLocation,
}, bsl); err != nil {
s.logger.WithError(errors.WithStack(err)).
Warnf("A backup storage location named %s has been specified for the server to use by default, but no corresponding backup storage location exists. Backups with a location not matching the default will need to explicitly specify an existing location", s.config.defaultBackupLocation)
}
@ -442,8 +469,12 @@ func (s *server) validateBackupStorageLocations() error {
pluginManager := clientmgmt.NewManager(s.logger, s.logLevel, s.pluginRegistry)
defer pluginManager.CleanupClients()
locations, err := s.veleroClient.VeleroV1().BackupStorageLocations(s.namespace).List(metav1.ListOptions{})
if err != nil {
// Fetching from the server directly since at this point
// the cache has not yet started
locations := &velerov1api.BackupStorageLocationList{}
if err := s.mgr.GetAPIReader().List(context.Background(), locations, &kbclient.ListOptions{
Namespace: s.namespace,
}); err != nil {
return errors.WithStack(err)
}
@ -543,7 +574,7 @@ func (s *server) initRestic() error {
secretsInformer,
s.sharedInformerFactory.Velero().V1().ResticRepositories(),
s.veleroClient.VeleroV1(),
s.sharedInformerFactory.Velero().V1().BackupStorageLocations(),
s.mgr.GetClient(),
s.kubeClient.CoreV1(),
s.kubeClient.CoreV1(),
s.logger,
@ -588,7 +619,6 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.logger.Info("Starting controllers")
ctx := s.ctx
var wg sync.WaitGroup
go func() {
metricsMux := http.NewServeMux()
@ -611,10 +641,9 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
backupSyncControllerRunInfo := func() controllerRunInfo {
backupSyncContoller := controller.NewBackupSyncController(
s.veleroClient.VeleroV1(),
s.veleroClient.VeleroV1(),
s.mgr.GetClient(),
s.veleroClient.VeleroV1(),
s.sharedInformerFactory.Velero().V1().Backups().Lister(),
s.sharedInformerFactory.Velero().V1().BackupStorageLocations().Lister(),
s.config.backupSyncPeriod,
s.namespace,
s.csiSnapshotClient,
@ -653,7 +682,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.logLevel,
newPluginManager,
backupTracker,
s.sharedInformerFactory.Velero().V1().BackupStorageLocations().Lister(),
s.mgr.GetClient(),
s.config.defaultBackupLocation,
s.config.defaultVolumesToRestic,
s.config.defaultBackupTTL,
@ -693,7 +722,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.sharedInformerFactory.Velero().V1().Backups(),
s.sharedInformerFactory.Velero().V1().DeleteBackupRequests().Lister(),
s.veleroClient.VeleroV1(),
s.sharedInformerFactory.Velero().V1().BackupStorageLocations().Lister(),
s.mgr.GetClient(),
)
return controllerRunInfo{
@ -713,7 +742,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
backupTracker,
s.resticManager,
s.sharedInformerFactory.Velero().V1().PodVolumeBackups().Lister(),
s.sharedInformerFactory.Velero().V1().BackupStorageLocations().Lister(),
s.mgr.GetClient(),
s.sharedInformerFactory.Velero().V1().VolumeSnapshotLocations().Lister(),
csiVSLister,
csiVSCLister,
@ -748,7 +777,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.veleroClient.VeleroV1(),
restorer,
s.sharedInformerFactory.Velero().V1().Backups().Lister(),
s.sharedInformerFactory.Velero().V1().BackupStorageLocations().Lister(),
s.mgr.GetClient(),
s.sharedInformerFactory.Velero().V1().VolumeSnapshotLocations().Lister(),
s.logger,
s.logLevel,
@ -769,7 +798,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.logger,
s.sharedInformerFactory.Velero().V1().ResticRepositories(),
s.veleroClient.VeleroV1(),
s.sharedInformerFactory.Velero().V1().BackupStorageLocations().Lister(),
s.mgr.GetClient(),
s.resticManager,
s.config.defaultResticMaintenanceFrequency,
)
@ -785,7 +814,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.veleroClient.VeleroV1(),
s.sharedInformerFactory.Velero().V1().DownloadRequests(),
s.sharedInformerFactory.Velero().V1().Restores().Lister(),
s.sharedInformerFactory.Velero().V1().BackupStorageLocations().Lister(),
s.mgr.GetClient(),
s.sharedInformerFactory.Velero().V1().Backups().Lister(),
newPluginManager,
s.logger,
@ -872,23 +901,22 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.logger.WithField("informer", informer).Info("Informer cache synced")
}
// now that the informer caches have all synced, we can start running the controllers
// TODO(2.0): presuming all controllers and resources are converted to runtime-controller
// by v2.0, the block from this line and including the `s.mgr.Start() will be
// deprecated, since the manager auto-starts all the caches. Until then, we need to start the
// cache for them manually.
for i := range controllers {
controllerRunInfo := controllers[i]
wg.Add(1)
go func() {
controllerRunInfo.controller.Run(ctx, controllerRunInfo.numWorkers)
wg.Done()
}()
// Adding the controllers to the manager will register them as a (runtime-controller) runnable,
// so the manager will ensure the cache is started and ready before all controller are started
s.mgr.Add(managercontroller.Runnable(controllerRunInfo.controller, controllerRunInfo.numWorkers))
}
s.logger.Info("Server started successfully")
s.logger.Info("Server starting...")
<-ctx.Done()
s.logger.Info("Waiting for all controllers to shut down gracefully")
wg.Wait()
if err := s.mgr.Start(s.ctx.Done()); err != nil {
s.logger.Fatal("Problem starting manager", err)
}
return nil
}

View File

@ -30,7 +30,7 @@ var (
{Name: "Name", Type: "string", Format: "name"},
{Name: "Provider"},
{Name: "Bucket/Prefix"},
{Name: "Status"},
{Name: "Phase"},
{Name: "Access Mode"},
}
)

View File

@ -19,6 +19,7 @@ package controller
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
@ -57,6 +58,8 @@ import (
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
"github.com/vmware-tanzu/velero/pkg/util/logging"
"github.com/vmware-tanzu/velero/pkg/volume"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
)
type backupController struct {
@ -65,11 +68,11 @@ type backupController struct {
backupper pkgbackup.Backupper
lister velerov1listers.BackupLister
client velerov1client.BackupsGetter
kbClient kbclient.Client
clock clock.Clock
backupLogLevel logrus.Level
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
backupTracker BackupTracker
backupLocationLister velerov1listers.BackupStorageLocationLister
defaultBackupLocation string
defaultVolumesToRestic bool
defaultBackupTTL time.Duration
@ -91,7 +94,7 @@ func NewBackupController(
backupLogLevel logrus.Level,
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
backupTracker BackupTracker,
backupLocationLister velerov1listers.BackupStorageLocationLister,
kbClient kbclient.Client,
defaultBackupLocation string,
defaultVolumesToRestic bool,
defaultBackupTTL time.Duration,
@ -112,7 +115,7 @@ func NewBackupController(
backupLogLevel: backupLogLevel,
newPluginManager: newPluginManager,
backupTracker: backupTracker,
backupLocationLister: backupLocationLister,
kbClient: kbClient,
defaultBackupLocation: defaultBackupLocation,
defaultVolumesToRestic: defaultVolumesToRestic,
defaultBackupTTL: defaultBackupTTL,
@ -371,7 +374,11 @@ func (c *backupController) prepareBackupRequest(backup *velerov1api.Backup) *pkg
}
// validate the storage location, and store the BackupStorageLocation API obj on the request
if storageLocation, err := c.backupLocationLister.BackupStorageLocations(request.Namespace).Get(request.Spec.StorageLocation); err != nil {
storageLocation := &velerov1api.BackupStorageLocation{}
if err := c.kbClient.Get(context.Background(), kbclient.ObjectKey{
Namespace: request.Namespace,
Name: request.Spec.StorageLocation,
}, storageLocation); err != nil {
if apierrors.IsNotFound(err) {
request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("a BackupStorageLocation CRD with the name specified in the backup spec needs to be created before this backup can be executed. Error: %v", err))
} else {
@ -385,7 +392,6 @@ func (c *backupController) prepareBackupRequest(backup *velerov1api.Backup) *pkg
fmt.Sprintf("backup can't be created because backup storage location %s is currently in read-only mode", request.StorageLocation.Name))
}
}
// validate and get the backup's VolumeSnapshotLocations, and store the
// VolumeSnapshotLocation API objs on the request
if locs, errs := c.validateAndGetSnapshotLocations(request.Backup); len(errs) > 0 {

View File

@ -18,8 +18,10 @@ package controller
import (
"bytes"
"context"
"fmt"
"io"
"sort"
"strings"
"testing"
@ -32,8 +34,8 @@ import (
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apimachinery/pkg/version"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
@ -154,7 +156,7 @@ func TestProcessBackupValidationFailures(t *testing.T) {
{
name: "non-existent backup location fails validation",
backup: defaultBackup().StorageLocation("nonexistent").Result(),
expectedErrs: []string{"a BackupStorageLocation CRD with the name specified in the backup spec needs to be created before this backup can be executed. Error: backupstoragelocation.velero.io \"nonexistent\" not found"},
expectedErrs: []string{"a BackupStorageLocation CRD with the name specified in the backup spec needs to be created before this backup can be executed. Error: backupstoragelocations.velero.io \"nonexistent\" not found"},
},
{
name: "backup for read-only backup location fails validation",
@ -177,12 +179,19 @@ func TestProcessBackupValidationFailures(t *testing.T) {
discoveryHelper, err := discovery.NewHelper(apiServer.DiscoveryClient, logger)
require.NoError(t, err)
var fakeClient kbclient.Client
if test.backupLocation != nil {
fakeClient = newFakeClient(t, test.backupLocation)
} else {
fakeClient = newFakeClient(t)
}
c := &backupController{
genericController: newGenericController("backup-test", logger),
discoveryHelper: discoveryHelper,
client: clientset.VeleroV1(),
lister: sharedInformers.Velero().V1().Backups().Lister(),
backupLocationLister: sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
kbClient: fakeClient,
snapshotLocationLister: sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
defaultBackupLocation: defaultBackupLocation.Name,
clock: &clock.RealClock{},
@ -192,13 +201,6 @@ func TestProcessBackupValidationFailures(t *testing.T) {
require.NotNil(t, test.backup)
require.NoError(t, sharedInformers.Velero().V1().Backups().Informer().GetStore().Add(test.backup))
if test.backupLocation != nil {
_, err := clientset.VeleroV1().BackupStorageLocations(test.backupLocation.Namespace).Create(test.backupLocation)
require.NoError(t, err)
require.NoError(t, sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(test.backupLocation))
}
require.NoError(t, c.processBackup(fmt.Sprintf("%s/%s", test.backup.Namespace, test.backup.Name)))
res, err := clientset.VeleroV1().Backups(test.backup.Namespace).Get(test.backup.Name, metav1.GetOptions{})
@ -244,6 +246,7 @@ func TestBackupLocationLabel(t *testing.T) {
clientset = fake.NewSimpleClientset(test.backup)
sharedInformers = informers.NewSharedInformerFactory(clientset, 0)
logger = logging.DefaultLogger(logrus.DebugLevel, formatFlag)
fakeClient = newFakeClient(t)
)
apiServer := velerotest.NewAPIServer(t)
@ -255,7 +258,7 @@ func TestBackupLocationLabel(t *testing.T) {
discoveryHelper: discoveryHelper,
client: clientset.VeleroV1(),
lister: sharedInformers.Velero().V1().Backups().Lister(),
backupLocationLister: sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
kbClient: fakeClient,
snapshotLocationLister: sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
defaultBackupLocation: test.backupLocation.Name,
clock: &clock.RealClock{},
@ -303,6 +306,7 @@ func TestDefaultBackupTTL(t *testing.T) {
formatFlag := logging.FormatText
var (
clientset = fake.NewSimpleClientset(test.backup)
fakeClient = newFakeClient(t)
logger = logging.DefaultLogger(logrus.DebugLevel, formatFlag)
sharedInformers = informers.NewSharedInformerFactory(clientset, 0)
)
@ -316,7 +320,7 @@ func TestDefaultBackupTTL(t *testing.T) {
c := &backupController{
genericController: newGenericController("backup-test", logger),
discoveryHelper: discoveryHelper,
backupLocationLister: sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
kbClient: fakeClient,
snapshotLocationLister: sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
defaultBackupTTL: defaultBackupTTL.Duration,
clock: clock.NewFakeClock(now),
@ -776,6 +780,14 @@ func TestProcessBackupCompletions(t *testing.T) {
backupper = new(fakeBackupper)
)
var fakeClient kbclient.Client
// add the test's backup storage location if it's different than the default
if test.backupLocation != nil && test.backupLocation != defaultBackupLocation {
fakeClient = newFakeClient(t, test.backupLocation)
} else {
fakeClient = newFakeClient(t)
}
apiServer := velerotest.NewAPIServer(t)
apiServer.DiscoveryClient.FakedServerVersion = &version.Info{
@ -798,7 +810,7 @@ func TestProcessBackupCompletions(t *testing.T) {
discoveryHelper: discoveryHelper,
client: clientset.VeleroV1(),
lister: sharedInformers.Velero().V1().Backups().Lister(),
backupLocationLister: sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
kbClient: fakeClient,
snapshotLocationLister: sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
defaultBackupLocation: defaultBackupLocation.Name,
defaultVolumesToRestic: test.defaultVolumesToRestic,
@ -833,19 +845,7 @@ func TestProcessBackupCompletions(t *testing.T) {
require.NoError(t, sharedInformers.Velero().V1().Backups().Informer().GetStore().Add(test.backup))
// add the default backup storage location to the clientset and the informer/lister store
_, err = clientset.VeleroV1().BackupStorageLocations(defaultBackupLocation.Namespace).Create(defaultBackupLocation)
require.NoError(t, err)
require.NoError(t, sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(defaultBackupLocation))
// add the test's backup storage location to the clientset and the informer/lister store
// if it's different than the default
if test.backupLocation != nil && test.backupLocation != defaultBackupLocation {
_, err := clientset.VeleroV1().BackupStorageLocations(test.backupLocation.Namespace).Create(test.backupLocation)
require.NoError(t, err)
require.NoError(t, sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(test.backupLocation))
}
require.NoError(t, fakeClient.Create(context.Background(), defaultBackupLocation))
require.NoError(t, c.processBackup(fmt.Sprintf("%s/%s", test.backup.Namespace, test.backup.Name)))

View File

@ -49,6 +49,8 @@ import (
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/restic"
"github.com/vmware-tanzu/velero/pkg/util/kube"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const resticTimeout = time.Minute
@ -64,7 +66,7 @@ type backupDeletionController struct {
backupTracker BackupTracker
resticMgr restic.RepositoryManager
podvolumeBackupLister velerov1listers.PodVolumeBackupLister
backupLocationLister velerov1listers.BackupStorageLocationLister
kbClient client.Client
snapshotLocationLister velerov1listers.VolumeSnapshotLocationLister
csiSnapshotLister snapshotv1beta1listers.VolumeSnapshotLister
csiSnapshotContentLister snapshotv1beta1listers.VolumeSnapshotContentLister
@ -87,7 +89,7 @@ func NewBackupDeletionController(
backupTracker BackupTracker,
resticMgr restic.RepositoryManager,
podvolumeBackupLister velerov1listers.PodVolumeBackupLister,
backupLocationLister velerov1listers.BackupStorageLocationLister,
kbClient client.Client,
snapshotLocationLister velerov1listers.VolumeSnapshotLocationLister,
csiSnapshotLister snapshotv1beta1listers.VolumeSnapshotLister,
csiSnapshotContentLister snapshotv1beta1listers.VolumeSnapshotContentLister,
@ -105,7 +107,7 @@ func NewBackupDeletionController(
backupTracker: backupTracker,
resticMgr: resticMgr,
podvolumeBackupLister: podvolumeBackupLister,
backupLocationLister: backupLocationLister,
kbClient: kbClient,
snapshotLocationLister: snapshotLocationLister,
csiSnapshotLister: csiSnapshotLister,
csiSnapshotContentLister: csiSnapshotContentLister,
@ -214,15 +216,18 @@ func (c *backupDeletionController) processRequest(req *velerov1api.DeleteBackupR
}
// Don't allow deleting backups in read-only storage locations
location, err := c.backupLocationLister.BackupStorageLocations(backup.Namespace).Get(backup.Spec.StorageLocation)
if apierrors.IsNotFound(err) {
_, err := c.patchDeleteBackupRequest(req, func(r *velerov1api.DeleteBackupRequest) {
r.Status.Phase = velerov1api.DeleteBackupRequestPhaseProcessed
r.Status.Errors = append(r.Status.Errors, fmt.Sprintf("backup storage location %s not found", backup.Spec.StorageLocation))
})
return err
}
if err != nil {
location := &velerov1api.BackupStorageLocation{}
if err := c.kbClient.Get(context.Background(), client.ObjectKey{
Namespace: backup.Namespace,
Name: backup.Spec.StorageLocation,
}, location); err != nil {
if apierrors.IsNotFound(err) {
_, err := c.patchDeleteBackupRequest(req, func(r *velerov1api.DeleteBackupRequest) {
r.Status.Phase = velerov1api.DeleteBackupRequestPhaseProcessed
r.Status.Errors = append(r.Status.Errors, fmt.Sprintf("backup storage location %s not found", backup.Spec.StorageLocation))
})
return err
}
return errors.Wrap(err, "error getting backup storage location")
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package controller
import (
"context"
"fmt"
"testing"
"time"
@ -34,8 +35,9 @@ import (
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apimachinery/pkg/util/sets"
core "k8s.io/client-go/testing"
"sigs.k8s.io/controller-runtime/pkg/client"
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/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/builder"
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/fake"
@ -63,7 +65,7 @@ func TestBackupDeletionControllerProcessQueueItem(t *testing.T) {
NewBackupTracker(),
nil, // restic repository manager
sharedInformers.Velero().V1().PodVolumeBackups().Lister(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
nil,
sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
nil, // csiSnapshotLister
nil, // csiSnapshotContentLister
@ -84,21 +86,21 @@ func TestBackupDeletionControllerProcessQueueItem(t *testing.T) {
req := pkgbackup.NewDeleteBackupRequest("foo", "uid")
req.Namespace = "foo"
req.Name = "foo-abcde"
req.Status.Phase = velerov1.DeleteBackupRequestPhaseProcessed
req.Status.Phase = velerov1api.DeleteBackupRequestPhaseProcessed
err = controller.processQueueItem("foo/bar")
assert.NoError(t, err)
// Invoke processRequestFunc
for _, phase := range []velerov1.DeleteBackupRequestPhase{"", velerov1.DeleteBackupRequestPhaseNew, velerov1.DeleteBackupRequestPhaseInProgress} {
for _, phase := range []velerov1api.DeleteBackupRequestPhase{"", velerov1api.DeleteBackupRequestPhaseNew, velerov1api.DeleteBackupRequestPhaseInProgress} {
t.Run(fmt.Sprintf("phase=%s", phase), func(t *testing.T) {
req.Status.Phase = phase
sharedInformers.Velero().V1().DeleteBackupRequests().Informer().GetStore().Add(req)
var errorToReturn error
var actual *velerov1.DeleteBackupRequest
var actual *velerov1api.DeleteBackupRequest
var called bool
controller.processRequestFunc = func(r *velerov1.DeleteBackupRequest) error {
controller.processRequestFunc = func(r *velerov1api.DeleteBackupRequest) error {
called = true
actual = r
return errorToReturn
@ -121,20 +123,22 @@ func TestBackupDeletionControllerProcessQueueItem(t *testing.T) {
type backupDeletionControllerTestData struct {
client *fake.Clientset
fakeClient client.Client
sharedInformers informers.SharedInformerFactory
volumeSnapshotter *velerotest.FakeVolumeSnapshotter
backupStore *persistencemocks.BackupStore
controller *backupDeletionController
req *velerov1.DeleteBackupRequest
req *velerov1api.DeleteBackupRequest
}
func setupBackupDeletionControllerTest(objects ...runtime.Object) *backupDeletionControllerTestData {
func setupBackupDeletionControllerTest(t *testing.T, objects ...runtime.Object) *backupDeletionControllerTestData {
req := pkgbackup.NewDeleteBackupRequest("foo", "uid")
req.Namespace = "velero"
req.Name = "foo-abcde"
var (
client = fake.NewSimpleClientset(append(objects, req)...)
fakeClient = newFakeClient(t, objects...)
sharedInformers = informers.NewSharedInformerFactory(client, 0)
volumeSnapshotter = &velerotest.FakeVolumeSnapshotter{SnapshotsTaken: sets.NewString()}
pluginManager = &pluginmocks.Manager{}
@ -143,6 +147,7 @@ func setupBackupDeletionControllerTest(objects ...runtime.Object) *backupDeletio
data := &backupDeletionControllerTestData{
client: client,
fakeClient: fakeClient,
sharedInformers: sharedInformers,
volumeSnapshotter: volumeSnapshotter,
backupStore: backupStore,
@ -156,7 +161,7 @@ func setupBackupDeletionControllerTest(objects ...runtime.Object) *backupDeletio
NewBackupTracker(),
nil, // restic repository manager
sharedInformers.Velero().V1().PodVolumeBackups().Lister(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
fakeClient,
sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
nil, // csiSnapshotLister
nil, // csiSnapshotContentLister
@ -168,7 +173,7 @@ func setupBackupDeletionControllerTest(objects ...runtime.Object) *backupDeletio
req: req,
}
data.controller.newBackupStore = func(*velerov1.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) {
data.controller.newBackupStore = func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) {
return backupStore, nil
}
@ -179,8 +184,7 @@ func setupBackupDeletionControllerTest(objects ...runtime.Object) *backupDeletio
func TestBackupDeletionControllerProcessRequest(t *testing.T) {
t.Run("missing spec.backupName", func(t *testing.T) {
td := setupBackupDeletionControllerTest()
td := setupBackupDeletionControllerTest(t)
td.req.Spec.BackupName = ""
err := td.controller.processRequest(td.req)
@ -188,7 +192,7 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
expectedActions := []core.Action{
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
@ -200,7 +204,7 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
})
t.Run("existing deletion requests for the backup are deleted", func(t *testing.T) {
td := setupBackupDeletionControllerTest()
td := setupBackupDeletionControllerTest(t)
// add the backup to the tracker so the execution of processRequest doesn't progress
// past checking for an in-progress backup. this makes validation easier.
@ -208,15 +212,15 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
require.NoError(t, td.sharedInformers.Velero().V1().DeleteBackupRequests().Informer().GetStore().Add(td.req))
existing := &velerov1.DeleteBackupRequest{
existing := &velerov1api.DeleteBackupRequest{
ObjectMeta: metav1.ObjectMeta{
Namespace: td.req.Namespace,
Name: "bar",
Labels: map[string]string{
velerov1.BackupNameLabel: td.req.Spec.BackupName,
velerov1api.BackupNameLabel: td.req.Spec.BackupName,
},
},
Spec: velerov1.DeleteBackupRequestSpec{
Spec: velerov1api.DeleteBackupRequestSpec{
BackupName: td.req.Spec.BackupName,
},
}
@ -225,15 +229,15 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
require.NoError(t, err)
require.NoError(t, td.sharedInformers.Velero().V1().DeleteBackupRequests().Informer().GetStore().Add(
&velerov1.DeleteBackupRequest{
&velerov1api.DeleteBackupRequest{
ObjectMeta: metav1.ObjectMeta{
Namespace: td.req.Namespace,
Name: "bar-2",
Labels: map[string]string{
velerov1.BackupNameLabel: "some-other-backup",
velerov1api.BackupNameLabel: "some-other-backup",
},
},
Spec: velerov1.DeleteBackupRequestSpec{
Spec: velerov1api.DeleteBackupRequestSpec{
BackupName: "some-other-backup",
},
},
@ -242,7 +246,7 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
assert.NoError(t, td.controller.processRequest(td.req))
expectedDeleteAction := core.NewDeleteAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
"bar",
)
@ -255,7 +259,7 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
})
t.Run("deleting an in progress backup isn't allowed", func(t *testing.T) {
td := setupBackupDeletionControllerTest()
td := setupBackupDeletionControllerTest(t)
td.controller.backupTracker.Add(td.req.Namespace, td.req.Spec.BackupName)
@ -264,7 +268,7 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
expectedActions := []core.Action{
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
@ -276,12 +280,10 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
})
t.Run("patching to InProgress fails", func(t *testing.T) {
backup := builder.ForBackup(velerov1.DefaultNamespace, "foo").StorageLocation("default").Result()
backup := builder.ForBackup(velerov1api.DefaultNamespace, "foo").StorageLocation("default").Result()
location := builder.ForBackupStorageLocation("velero", "default").Result()
td := setupBackupDeletionControllerTest(backup)
td.sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(location)
td := setupBackupDeletionControllerTest(t, location, backup)
td.client.PrependReactor("patch", "deletebackuprequests", func(action core.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("bad")
@ -292,12 +294,12 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
expectedActions := []core.Action{
core.NewGetAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
backup.Namespace,
backup.Name,
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
@ -308,12 +310,10 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
})
t.Run("patching backup to Deleting fails", func(t *testing.T) {
backup := builder.ForBackup(velerov1.DefaultNamespace, "foo").StorageLocation("default").Result()
backup := builder.ForBackup(velerov1api.DefaultNamespace, "foo").StorageLocation("default").Result()
location := builder.ForBackupStorageLocation("velero", "default").Result()
td := setupBackupDeletionControllerTest(backup)
td.sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(location)
td := setupBackupDeletionControllerTest(t, location, backup)
td.client.PrependReactor("patch", "deletebackuprequests", func(action core.Action) (bool, runtime.Object, error) {
return true, td.req, nil
@ -327,19 +327,19 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
expectedActions := []core.Action{
core.NewGetAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
backup.Namespace,
backup.Name,
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
[]byte(`{"status":{"phase":"InProgress"}}`),
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
backup.Namespace,
backup.Name,
types.MergePatchType,
@ -350,19 +350,19 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
})
t.Run("unable to find backup", func(t *testing.T) {
td := setupBackupDeletionControllerTest()
td := setupBackupDeletionControllerTest(t)
err := td.controller.processRequest(td.req)
require.NoError(t, err)
expectedActions := []core.Action{
core.NewGetAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
td.req.Namespace,
td.req.Spec.BackupName,
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
@ -374,21 +374,21 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
})
t.Run("unable to find backup storage location", func(t *testing.T) {
backup := builder.ForBackup(velerov1.DefaultNamespace, "foo").StorageLocation("default").Result()
backup := builder.ForBackup(velerov1api.DefaultNamespace, "foo").StorageLocation("default").Result()
td := setupBackupDeletionControllerTest(backup)
td := setupBackupDeletionControllerTest(t, backup)
err := td.controller.processRequest(td.req)
require.NoError(t, err)
expectedActions := []core.Action{
core.NewGetAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
td.req.Namespace,
td.req.Spec.BackupName,
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
@ -400,24 +400,22 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
})
t.Run("backup storage location is in read-only mode", func(t *testing.T) {
backup := builder.ForBackup(velerov1.DefaultNamespace, "foo").StorageLocation("default").Result()
location := builder.ForBackupStorageLocation("velero", "default").AccessMode(velerov1.BackupStorageLocationAccessModeReadOnly).Result()
backup := builder.ForBackup(velerov1api.DefaultNamespace, "foo").StorageLocation("default").Result()
location := builder.ForBackupStorageLocation("velero", "default").AccessMode(velerov1api.BackupStorageLocationAccessModeReadOnly).Result()
td := setupBackupDeletionControllerTest(backup)
td.sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(location)
td := setupBackupDeletionControllerTest(t, location, backup)
err := td.controller.processRequest(td.req)
require.NoError(t, err)
expectedActions := []core.Action{
core.NewGetAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
td.req.Namespace,
td.req.Spec.BackupName,
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
@ -429,42 +427,43 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
})
t.Run("full delete, no errors", func(t *testing.T) {
backup := builder.ForBackup(velerov1.DefaultNamespace, "foo").Result()
backup := builder.ForBackup(velerov1api.DefaultNamespace, "foo").Result()
backup.UID = "uid"
backup.Spec.StorageLocation = "primary"
restore1 := builder.ForRestore("velero", "restore-1").Phase(velerov1.RestorePhaseCompleted).Backup("foo").Result()
restore2 := builder.ForRestore("velero", "restore-2").Phase(velerov1.RestorePhaseCompleted).Backup("foo").Result()
restore3 := builder.ForRestore("velero", "restore-3").Phase(velerov1.RestorePhaseCompleted).Backup("some-other-backup").Result()
restore1 := builder.ForRestore("velero", "restore-1").Phase(velerov1api.RestorePhaseCompleted).Backup("foo").Result()
restore2 := builder.ForRestore("velero", "restore-2").Phase(velerov1api.RestorePhaseCompleted).Backup("foo").Result()
restore3 := builder.ForRestore("velero", "restore-3").Phase(velerov1api.RestorePhaseCompleted).Backup("some-other-backup").Result()
td := setupBackupDeletionControllerTest(backup, restore1, restore2, restore3)
td := setupBackupDeletionControllerTest(t, backup, restore1, restore2, restore3)
td.sharedInformers.Velero().V1().Restores().Informer().GetStore().Add(restore1)
td.sharedInformers.Velero().V1().Restores().Informer().GetStore().Add(restore2)
td.sharedInformers.Velero().V1().Restores().Informer().GetStore().Add(restore3)
location := &velerov1.BackupStorageLocation{
location := &velerov1api.BackupStorageLocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: backup.Namespace,
Name: backup.Spec.StorageLocation,
},
Spec: velerov1.BackupStorageLocationSpec{
Spec: velerov1api.BackupStorageLocationSpec{
Provider: "objStoreProvider",
StorageType: velerov1.StorageType{
ObjectStorage: &velerov1.ObjectStorageLocation{
StorageType: velerov1api.StorageType{
ObjectStorage: &velerov1api.ObjectStorageLocation{
Bucket: "bucket",
},
},
},
}
require.NoError(t, td.sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(location))
snapshotLocation := &velerov1.VolumeSnapshotLocation{
require.NoError(t, td.fakeClient.Create(context.Background(), location))
snapshotLocation := &velerov1api.VolumeSnapshotLocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: backup.Namespace,
Name: "vsl-1",
},
Spec: velerov1.VolumeSnapshotLocationSpec{
Spec: velerov1api.VolumeSnapshotLocationSpec{
Provider: "provider-1",
},
}
@ -514,55 +513,55 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
expectedActions := []core.Action{
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
[]byte(`{"metadata":{"labels":{"velero.io/backup-name":"foo"}},"status":{"phase":"InProgress"}}`),
),
core.NewGetAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
td.req.Namespace,
td.req.Spec.BackupName,
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
[]byte(`{"metadata":{"labels":{"velero.io/backup-uid":"uid"}}}`),
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
td.req.Namespace,
td.req.Spec.BackupName,
types.MergePatchType,
[]byte(`{"status":{"phase":"Deleting"}}`),
),
core.NewDeleteAction(
velerov1.SchemeGroupVersion.WithResource("restores"),
velerov1api.SchemeGroupVersion.WithResource("restores"),
td.req.Namespace,
"restore-1",
),
core.NewDeleteAction(
velerov1.SchemeGroupVersion.WithResource("restores"),
velerov1api.SchemeGroupVersion.WithResource("restores"),
td.req.Namespace,
"restore-2",
),
core.NewDeleteAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
td.req.Namespace,
td.req.Spec.BackupName,
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
[]byte(`{"status":{"phase":"Processed"}}`),
),
core.NewDeleteCollectionAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
pkgbackup.NewDeleteBackupRequestListOptions(td.req.Spec.BackupName, "uid"),
),
@ -584,19 +583,19 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
backup.Spec.StorageLocation = "primary"
restore1 := builder.ForRestore("velero", "restore-1").
Phase(velerov1.RestorePhaseCompleted).
Phase(velerov1api.RestorePhaseCompleted).
Backup("the-really-long-backup-name-that-is-much-more-than-63-characters").
Result()
restore2 := builder.ForRestore("velero", "restore-2").
Phase(velerov1.RestorePhaseCompleted).
Phase(velerov1api.RestorePhaseCompleted).
Backup("the-really-long-backup-name-that-is-much-more-than-63-characters").
Result()
restore3 := builder.ForRestore("velero", "restore-3").
Phase(velerov1.RestorePhaseCompleted).
Phase(velerov1api.RestorePhaseCompleted).
Backup("some-other-backup").
Result()
td := setupBackupDeletionControllerTest(backup, restore1, restore2, restore3)
td := setupBackupDeletionControllerTest(t, backup, restore1, restore2, restore3)
td.req = pkgbackup.NewDeleteBackupRequest(backup.Name, string(backup.UID))
td.req.Namespace = "velero"
td.req.Name = "foo-abcde"
@ -604,28 +603,28 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
td.sharedInformers.Velero().V1().Restores().Informer().GetStore().Add(restore2)
td.sharedInformers.Velero().V1().Restores().Informer().GetStore().Add(restore3)
location := &velerov1.BackupStorageLocation{
location := &velerov1api.BackupStorageLocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: backup.Namespace,
Name: backup.Spec.StorageLocation,
},
Spec: velerov1.BackupStorageLocationSpec{
Spec: velerov1api.BackupStorageLocationSpec{
Provider: "objStoreProvider",
StorageType: velerov1.StorageType{
ObjectStorage: &velerov1.ObjectStorageLocation{
StorageType: velerov1api.StorageType{
ObjectStorage: &velerov1api.ObjectStorageLocation{
Bucket: "bucket",
},
},
},
}
require.NoError(t, td.sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(location))
require.NoError(t, td.fakeClient.Create(context.Background(), location))
snapshotLocation := &velerov1.VolumeSnapshotLocation{
snapshotLocation := &velerov1api.VolumeSnapshotLocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: backup.Namespace,
Name: "vsl-1",
},
Spec: velerov1.VolumeSnapshotLocationSpec{
Spec: velerov1api.VolumeSnapshotLocationSpec{
Provider: "provider-1",
},
}
@ -673,55 +672,55 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
expectedActions := []core.Action{
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
[]byte(`{"metadata":{"labels":{"velero.io/backup-name":"the-really-long-backup-name-that-is-much-more-than-63-cha6ca4bc"}},"status":{"phase":"InProgress"}}`),
),
core.NewGetAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
td.req.Namespace,
td.req.Spec.BackupName,
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
[]byte(`{"metadata":{"labels":{"velero.io/backup-uid":"uid"}}}`),
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
td.req.Namespace,
td.req.Spec.BackupName,
types.MergePatchType,
[]byte(`{"status":{"phase":"Deleting"}}`),
),
core.NewDeleteAction(
velerov1.SchemeGroupVersion.WithResource("restores"),
velerov1api.SchemeGroupVersion.WithResource("restores"),
td.req.Namespace,
"restore-1",
),
core.NewDeleteAction(
velerov1.SchemeGroupVersion.WithResource("restores"),
velerov1api.SchemeGroupVersion.WithResource("restores"),
td.req.Namespace,
"restore-2",
),
core.NewDeleteAction(
velerov1.SchemeGroupVersion.WithResource("backups"),
velerov1api.SchemeGroupVersion.WithResource("backups"),
td.req.Namespace,
td.req.Spec.BackupName,
),
core.NewPatchAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
td.req.Name,
types.MergePatchType,
[]byte(`{"status":{"phase":"Processed"}}`),
),
core.NewDeleteCollectionAction(
velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"),
velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"),
td.req.Namespace,
pkgbackup.NewDeleteBackupRequestListOptions(td.req.Spec.BackupName, "uid"),
),
@ -735,6 +734,7 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
}
func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
now := time.Date(2018, 4, 4, 12, 0, 0, 0, time.UTC)
unexpired1 := time.Date(2018, 4, 4, 11, 0, 0, 0, time.UTC)
unexpired2 := time.Date(2018, 4, 3, 12, 0, 1, 0, time.UTC)
@ -743,7 +743,7 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
tests := []struct {
name string
requests []*velerov1.DeleteBackupRequest
requests []*velerov1api.DeleteBackupRequest
expectedDeletions []string
}{
{
@ -751,14 +751,14 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
},
{
name: "older than max age, phase = '', don't delete",
requests: []*velerov1.DeleteBackupRequest{
requests: []*velerov1api.DeleteBackupRequest{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns",
Name: "name",
CreationTimestamp: metav1.Time{Time: expired1},
},
Status: velerov1.DeleteBackupRequestStatus{
Status: velerov1api.DeleteBackupRequestStatus{
Phase: "",
},
},
@ -766,45 +766,45 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
},
{
name: "older than max age, phase = New, don't delete",
requests: []*velerov1.DeleteBackupRequest{
requests: []*velerov1api.DeleteBackupRequest{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns",
Name: "name",
CreationTimestamp: metav1.Time{Time: expired1},
},
Status: velerov1.DeleteBackupRequestStatus{
Phase: velerov1.DeleteBackupRequestPhaseNew,
Status: velerov1api.DeleteBackupRequestStatus{
Phase: velerov1api.DeleteBackupRequestPhaseNew,
},
},
},
},
{
name: "older than max age, phase = InProcess, don't delete",
requests: []*velerov1.DeleteBackupRequest{
requests: []*velerov1api.DeleteBackupRequest{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns",
Name: "name",
CreationTimestamp: metav1.Time{Time: expired1},
},
Status: velerov1.DeleteBackupRequestStatus{
Phase: velerov1.DeleteBackupRequestPhaseInProgress,
Status: velerov1api.DeleteBackupRequestStatus{
Phase: velerov1api.DeleteBackupRequestPhaseInProgress,
},
},
},
},
{
name: "some expired, some not",
requests: []*velerov1.DeleteBackupRequest{
requests: []*velerov1api.DeleteBackupRequest{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns",
Name: "unexpired-1",
CreationTimestamp: metav1.Time{Time: unexpired1},
},
Status: velerov1.DeleteBackupRequestStatus{
Phase: velerov1.DeleteBackupRequestPhaseProcessed,
Status: velerov1api.DeleteBackupRequestStatus{
Phase: velerov1api.DeleteBackupRequestPhaseProcessed,
},
},
{
@ -813,8 +813,8 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
Name: "expired-1",
CreationTimestamp: metav1.Time{Time: expired1},
},
Status: velerov1.DeleteBackupRequestStatus{
Phase: velerov1.DeleteBackupRequestPhaseProcessed,
Status: velerov1api.DeleteBackupRequestStatus{
Phase: velerov1api.DeleteBackupRequestPhaseProcessed,
},
},
{
@ -823,8 +823,8 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
Name: "unexpired-2",
CreationTimestamp: metav1.Time{Time: unexpired2},
},
Status: velerov1.DeleteBackupRequestStatus{
Phase: velerov1.DeleteBackupRequestPhaseProcessed,
Status: velerov1api.DeleteBackupRequestStatus{
Phase: velerov1api.DeleteBackupRequestPhaseProcessed,
},
},
{
@ -833,8 +833,8 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
Name: "expired-2",
CreationTimestamp: metav1.Time{Time: expired2},
},
Status: velerov1.DeleteBackupRequestStatus{
Phase: velerov1.DeleteBackupRequestPhaseProcessed,
Status: velerov1api.DeleteBackupRequestStatus{
Phase: velerov1api.DeleteBackupRequestPhaseProcessed,
},
},
},
@ -845,6 +845,7 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
client := fake.NewSimpleClientset()
fakeClient := newFakeClient(t)
sharedInformers := informers.NewSharedInformerFactory(client, 0)
controller := NewBackupDeletionController(
@ -857,7 +858,7 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
NewBackupTracker(),
nil,
sharedInformers.Velero().V1().PodVolumeBackups().Lister(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
fakeClient,
sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
nil, // csiSnapshotLister
nil, // csiSnapshotContentLister
@ -878,7 +879,7 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
expectedActions := []core.Action{}
for _, name := range test.expectedDeletions {
expectedActions = append(expectedActions, core.NewDeleteAction(velerov1.SchemeGroupVersion.WithResource("deletebackuprequests"), "ns", name))
expectedActions = append(expectedActions, core.NewDeleteAction(velerov1api.SchemeGroupVersion.WithResource("deletebackuprequests"), "ns", name))
}
velerotest.CompareActions(t, expectedActions, client.Actions())
@ -981,7 +982,7 @@ func TestDeleteCSIVolumeSnapshots(t *testing.T) {
Name: "vs1",
Namespace: "ns1",
Labels: map[string]string{
velerov1.BackupNameLabel: "backup1",
velerov1api.BackupNameLabel: "backup1",
},
},
Status: &snapshotv1beta1api.VolumeSnapshotStatus{
@ -1003,7 +1004,7 @@ func TestDeleteCSIVolumeSnapshots(t *testing.T) {
Name: "vs2",
Namespace: "ns1",
Labels: map[string]string{
velerov1.BackupNameLabel: "backup1",
velerov1api.BackupNameLabel: "backup1",
},
},
Status: &snapshotv1beta1api.VolumeSnapshotStatus{
@ -1025,7 +1026,7 @@ func TestDeleteCSIVolumeSnapshots(t *testing.T) {
Name: "vs1",
Namespace: "ns2",
Labels: map[string]string{
velerov1.BackupNameLabel: "backup1",
velerov1api.BackupNameLabel: "backup1",
},
},
Status: &snapshotv1beta1api.VolumeSnapshotStatus{
@ -1047,7 +1048,7 @@ func TestDeleteCSIVolumeSnapshots(t *testing.T) {
Name: "vs2",
Namespace: "ns2",
Labels: map[string]string{
velerov1.BackupNameLabel: "backup1",
velerov1api.BackupNameLabel: "backup1",
},
},
Status: &snapshotv1beta1api.VolumeSnapshotStatus{
@ -1061,7 +1062,7 @@ func TestDeleteCSIVolumeSnapshots(t *testing.T) {
Name: "ns1NilStatusVS",
Namespace: "ns2",
Labels: map[string]string{
velerov1.BackupNameLabel: "backup2",
velerov1api.BackupNameLabel: "backup2",
},
},
Status: nil,
@ -1073,7 +1074,7 @@ func TestDeleteCSIVolumeSnapshots(t *testing.T) {
Name: "ns1NilBoundVSCVS",
Namespace: "ns2",
Labels: map[string]string{
velerov1.BackupNameLabel: "backup3",
velerov1api.BackupNameLabel: "backup3",
},
},
Status: &snapshotv1beta1api.VolumeSnapshotStatus{
@ -1088,7 +1089,7 @@ func TestDeleteCSIVolumeSnapshots(t *testing.T) {
Name: "ns1NonExistentVSCVS",
Namespace: "ns2",
Labels: map[string]string{
velerov1.BackupNameLabel: "backup3",
velerov1api.BackupNameLabel: "backup3",
},
},
Status: &snapshotv1beta1api.VolumeSnapshotStatus{
@ -1151,7 +1152,7 @@ func TestDeleteCSIVolumeSnapshotContents(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "retainVSC",
Labels: map[string]string{
velerov1.BackupNameLabel: "backup1",
velerov1api.BackupNameLabel: "backup1",
},
},
Spec: snapshotv1beta1api.VolumeSnapshotContentSpec{
@ -1162,7 +1163,7 @@ func TestDeleteCSIVolumeSnapshotContents(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "deleteVSC",
Labels: map[string]string{
velerov1.BackupNameLabel: "backup2",
velerov1api.BackupNameLabel: "backup2",
},
},
Spec: snapshotv1beta1api.VolumeSnapshotContentSpec{
@ -1174,7 +1175,7 @@ func TestDeleteCSIVolumeSnapshotContents(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "nothingVSC",
Labels: map[string]string{
velerov1.BackupNameLabel: "backup3",
velerov1api.BackupNameLabel: "backup3",
},
},
Spec: snapshotv1beta1api.VolumeSnapshotContentSpec{},

View File

@ -17,15 +17,15 @@ limitations under the License.
package controller
import (
"encoding/json"
"context"
"time"
snapshotterClientSet "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/clientset/versioned"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
kuberrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/kubernetes"
@ -36,31 +36,31 @@ import (
"github.com/vmware-tanzu/velero/pkg/label"
"github.com/vmware-tanzu/velero/pkg/persistence"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type backupSyncController struct {
*genericController
backupClient velerov1client.BackupsGetter
backupLocationClient velerov1client.BackupStorageLocationsGetter
podVolumeBackupClient velerov1client.PodVolumeBackupsGetter
backupLister velerov1listers.BackupLister
csiSnapshotClient *snapshotterClientSet.Clientset
kubeClient kubernetes.Interface
backupStorageLocationLister velerov1listers.BackupStorageLocationLister
namespace string
defaultBackupLocation string
defaultBackupSyncPeriod time.Duration
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
newBackupStore func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error)
backupClient velerov1client.BackupsGetter
kbClient client.Client
podVolumeBackupClient velerov1client.PodVolumeBackupsGetter
backupLister velerov1listers.BackupLister
csiSnapshotClient *snapshotterClientSet.Clientset
kubeClient kubernetes.Interface
namespace string
defaultBackupLocation string
defaultBackupSyncPeriod time.Duration
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
newBackupStore func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error)
}
func NewBackupSyncController(
backupClient velerov1client.BackupsGetter,
backupLocationClient velerov1client.BackupStorageLocationsGetter,
kbClient client.Client,
podVolumeBackupClient velerov1client.PodVolumeBackupsGetter,
backupLister velerov1listers.BackupLister,
backupStorageLocationLister velerov1listers.BackupStorageLocationLister,
syncPeriod time.Duration,
namespace string,
csiSnapshotClient *snapshotterClientSet.Clientset,
@ -75,17 +75,16 @@ func NewBackupSyncController(
logger.Infof("Backup sync period is %v", syncPeriod)
c := &backupSyncController{
genericController: newGenericController("backup-sync", logger),
backupClient: backupClient,
backupLocationClient: backupLocationClient,
podVolumeBackupClient: podVolumeBackupClient,
namespace: namespace,
defaultBackupLocation: defaultBackupLocation,
defaultBackupSyncPeriod: syncPeriod,
backupLister: backupLister,
backupStorageLocationLister: backupStorageLocationLister,
csiSnapshotClient: csiSnapshotClient,
kubeClient: kubeClient,
genericController: newGenericController("backup-sync", logger),
backupClient: backupClient,
kbClient: kbClient,
podVolumeBackupClient: podVolumeBackupClient,
namespace: namespace,
defaultBackupLocation: defaultBackupLocation,
defaultBackupSyncPeriod: syncPeriod,
backupLister: backupLister,
csiSnapshotClient: csiSnapshotClient,
kubeClient: kubeClient,
// use variables to refer to these functions so they can be
// replaced with fakes for testing.
@ -101,35 +100,38 @@ func NewBackupSyncController(
// orderedBackupLocations returns a new slice with the default backup location first (if it exists),
// followed by the rest of the locations in no particular order.
func orderedBackupLocations(locations []*velerov1api.BackupStorageLocation, defaultLocationName string) []*velerov1api.BackupStorageLocation {
var result []*velerov1api.BackupStorageLocation
func orderedBackupLocations(locationList *velerov1api.BackupStorageLocationList, defaultLocationName string) []velerov1api.BackupStorageLocation {
var result []velerov1api.BackupStorageLocation
for i := range locations {
if locations[i].Name == defaultLocationName {
for i := range locationList.Items {
if locationList.Items[i].Name == defaultLocationName {
// put the default location first
result = append(result, locations[i])
result = append(result, locationList.Items[i])
// append everything before the default
result = append(result, locations[:i]...)
result = append(result, locationList.Items[:i]...)
// append everything after the default
result = append(result, locations[i+1:]...)
result = append(result, locationList.Items[i+1:]...)
return result
}
}
return locations
return locationList.Items
}
func (c *backupSyncController) run() {
c.logger.Debug("Checking for existing backup storage locations to sync into cluster")
locations, err := c.backupStorageLocationLister.BackupStorageLocations(c.namespace).List(labels.Everything())
if err != nil {
locationList := &velerov1api.BackupStorageLocationList{}
if err := c.kbClient.List(context.Background(), locationList, &client.ListOptions{
Namespace: c.namespace,
}); err != nil {
c.logger.WithError(errors.WithStack(err)).Error("Error getting backup storage locations from lister")
return
}
// sync the default location first, if it exists
locations = orderedBackupLocations(locations, c.defaultBackupLocation)
locations := orderedBackupLocations(locationList, c.defaultBackupLocation)
pluginManager := c.newPluginManager(c.logger)
defer pluginManager.CleanupClients()
@ -162,7 +164,7 @@ func (c *backupSyncController) run() {
log.Debug("Checking backup location for backups to sync into cluster")
backupStore, err := c.newBackupStore(location, pluginManager, log)
backupStore, err := c.newBackupStore(&location, pluginManager, log)
if err != nil {
log.WithError(err).Error("Error getting backup store for this location")
continue
@ -304,23 +306,9 @@ func (c *backupSyncController) run() {
c.deleteOrphanedBackups(location.Name, backupStoreBackups, log)
// update the location's last-synced time field
patch := map[string]interface{}{
"status": map[string]interface{}{
"lastSyncedTime": time.Now().UTC(),
},
}
patchBytes, err := json.Marshal(patch)
if err != nil {
log.WithError(errors.WithStack(err)).Error("Error marshaling last-synced patch to JSON")
continue
}
if _, err = c.backupLocationClient.BackupStorageLocations(c.namespace).Patch(
location.Name,
types.MergePatchType,
patchBytes,
); err != nil {
statusPatch := client.MergeFrom(location.DeepCopyObject())
location.Status.LastSyncedTime = &metav1.Time{Time: time.Now().UTC()}
if err := c.kbClient.Status().Patch(context.Background(), &location, statusPatch); err != nil {
log.WithError(errors.WithStack(err)).Error("Error patching backup location's last-synced time")
continue
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package controller
import (
"context"
"testing"
"time"
@ -332,6 +333,7 @@ func TestBackupSyncControllerRun(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var (
client = fake.NewSimpleClientset()
fakeClient = newFakeClient(t)
sharedInformers = informers.NewSharedInformerFactory(client, 0)
pluginManager = &pluginmocks.Manager{}
backupStores = make(map[string]*persistencemocks.BackupStore)
@ -339,10 +341,9 @@ func TestBackupSyncControllerRun(t *testing.T) {
c := NewBackupSyncController(
client.VeleroV1(),
client.VeleroV1(),
fakeClient,
client.VeleroV1(),
sharedInformers.Velero().V1().Backups().Lister(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
time.Duration(0),
test.namespace,
nil, // csiSnapshotClient
@ -360,7 +361,7 @@ func TestBackupSyncControllerRun(t *testing.T) {
pluginManager.On("CleanupClients").Return(nil)
for _, location := range test.locations {
require.NoError(t, sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(location))
require.NoError(t, fakeClient.Create(context.Background(), location))
backupStores[location.Name] = &persistencemocks.BackupStore{}
}
@ -559,15 +560,15 @@ func TestDeleteOrphanedBackups(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var (
client = fake.NewSimpleClientset()
fakeClient = newFakeClient(t)
sharedInformers = informers.NewSharedInformerFactory(client, 0)
)
c := NewBackupSyncController(
client.VeleroV1(),
client.VeleroV1(),
fakeClient,
client.VeleroV1(),
sharedInformers.Velero().V1().Backups().Lister(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
time.Duration(0),
test.namespace,
nil, // csiSnapshotClient
@ -652,15 +653,15 @@ func TestStorageLabelsInDeleteOrphanedBackups(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var (
client = fake.NewSimpleClientset()
fakeClient = newFakeClient(t)
sharedInformers = informers.NewSharedInformerFactory(client, 0)
)
c := NewBackupSyncController(
client.VeleroV1(),
client.VeleroV1(),
fakeClient,
client.VeleroV1(),
sharedInformers.Velero().V1().Backups().Lister(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
time.Duration(0),
test.namespace,
nil, // csiSnapshotClient

View File

@ -17,6 +17,7 @@ limitations under the License.
package controller
import (
"context"
"encoding/json"
"time"
@ -29,8 +30,9 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
velerov1informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions/velero/v1"
velerov1listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1"
@ -46,10 +48,10 @@ type downloadRequestController struct {
downloadRequestLister velerov1listers.DownloadRequestLister
restoreLister velerov1listers.RestoreLister
clock clock.Clock
backupLocationLister velerov1listers.BackupStorageLocationLister
kbClient client.Client
backupLister velerov1listers.BackupLister
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
newBackupStore func(*v1.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error)
newBackupStore func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error)
}
// NewDownloadRequestController creates a new DownloadRequestController.
@ -57,7 +59,7 @@ func NewDownloadRequestController(
downloadRequestClient velerov1client.DownloadRequestsGetter,
downloadRequestInformer velerov1informers.DownloadRequestInformer,
restoreLister velerov1listers.RestoreLister,
backupLocationLister velerov1listers.BackupStorageLocationLister,
kbClient client.Client,
backupLister velerov1listers.BackupLister,
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
logger logrus.FieldLogger,
@ -67,7 +69,7 @@ func NewDownloadRequestController(
downloadRequestClient: downloadRequestClient,
downloadRequestLister: downloadRequestInformer.Lister(),
restoreLister: restoreLister,
backupLocationLister: backupLocationLister,
kbClient: kbClient,
backupLister: backupLister,
// use variables to refer to these functions so they can be
@ -85,7 +87,7 @@ func NewDownloadRequestController(
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err != nil {
downloadRequest := obj.(*v1.DownloadRequest)
downloadRequest := obj.(*velerov1api.DownloadRequest)
c.logger.WithError(errors.WithStack(err)).
WithField("downloadRequest", downloadRequest.Name).
Error("Error creating queue key, item not added to queue")
@ -121,9 +123,9 @@ func (c *downloadRequestController) processDownloadRequest(key string) error {
}
switch downloadRequest.Status.Phase {
case "", v1.DownloadRequestPhaseNew:
case "", velerov1api.DownloadRequestPhaseNew:
return c.generatePreSignedURL(downloadRequest, log)
case v1.DownloadRequestPhaseProcessed:
case velerov1api.DownloadRequestPhaseProcessed:
return c.deleteIfExpired(downloadRequest)
}
@ -134,7 +136,7 @@ const signedURLTTL = 10 * time.Minute
// generatePreSignedURL generates a pre-signed URL for downloadRequest, changes the phase to
// Processed, and persists the changes to storage.
func (c *downloadRequestController) generatePreSignedURL(downloadRequest *v1.DownloadRequest, log logrus.FieldLogger) error {
func (c *downloadRequestController) generatePreSignedURL(downloadRequest *velerov1api.DownloadRequest, log logrus.FieldLogger) error {
update := downloadRequest.DeepCopy()
var (
@ -143,7 +145,7 @@ func (c *downloadRequestController) generatePreSignedURL(downloadRequest *v1.Dow
)
switch downloadRequest.Spec.Target.Kind {
case v1.DownloadTargetKindRestoreLog, v1.DownloadTargetKindRestoreResults:
case velerov1api.DownloadTargetKindRestoreLog, velerov1api.DownloadTargetKindRestoreResults:
restore, err := c.restoreLister.Restores(downloadRequest.Namespace).Get(downloadRequest.Spec.Target.Name)
if err != nil {
return errors.Wrap(err, "error getting Restore")
@ -159,8 +161,11 @@ func (c *downloadRequestController) generatePreSignedURL(downloadRequest *v1.Dow
return errors.WithStack(err)
}
backupLocation, err := c.backupLocationLister.BackupStorageLocations(backup.Namespace).Get(backup.Spec.StorageLocation)
if err != nil {
backupLocation := &velerov1api.BackupStorageLocation{}
if err := c.kbClient.Get(context.Background(), client.ObjectKey{
Namespace: backup.Namespace,
Name: backup.Spec.StorageLocation,
}, backupLocation); err != nil {
return errors.WithStack(err)
}
@ -176,7 +181,7 @@ func (c *downloadRequestController) generatePreSignedURL(downloadRequest *v1.Dow
return err
}
update.Status.Phase = v1.DownloadRequestPhaseProcessed
update.Status.Phase = velerov1api.DownloadRequestPhaseProcessed
update.Status.Expiration = &metav1.Time{Time: c.clock.Now().Add(persistence.DownloadURLTTL)}
_, err = patchDownloadRequest(downloadRequest, update, c.downloadRequestClient)
@ -184,7 +189,7 @@ func (c *downloadRequestController) generatePreSignedURL(downloadRequest *v1.Dow
}
// deleteIfExpired deletes downloadRequest if it has expired.
func (c *downloadRequestController) deleteIfExpired(downloadRequest *v1.DownloadRequest) error {
func (c *downloadRequestController) deleteIfExpired(downloadRequest *velerov1api.DownloadRequest) error {
log := c.logger.WithField("key", kube.NamespaceAndName(downloadRequest))
log.Info("checking for expiration of DownloadRequest")
if downloadRequest.Status.Expiration.Time.After(c.clock.Now()) {
@ -216,7 +221,7 @@ func (c *downloadRequestController) resync() {
}
}
func patchDownloadRequest(original, updated *v1.DownloadRequest, client velerov1client.DownloadRequestsGetter) (*v1.DownloadRequest, error) {
func patchDownloadRequest(original, updated *velerov1api.DownloadRequest, client velerov1client.DownloadRequestsGetter) (*velerov1api.DownloadRequest, error) {
origBytes, err := json.Marshal(original)
if err != nil {
return nil, errors.Wrap(err, "error marshalling original download request")

View File

@ -27,7 +27,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/clock"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
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/fake"
informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions"
@ -48,7 +50,7 @@ type downloadRequestTestHarness struct {
controller *downloadRequestController
}
func newDownloadRequestTestHarness(t *testing.T) *downloadRequestTestHarness {
func newDownloadRequestTestHarness(t *testing.T, fakeClient client.Client) *downloadRequestTestHarness {
var (
client = fake.NewSimpleClientset()
informerFactory = informers.NewSharedInformerFactory(client, 0)
@ -58,7 +60,7 @@ func newDownloadRequestTestHarness(t *testing.T) *downloadRequestTestHarness {
client.VeleroV1(),
informerFactory.Velero().V1().DownloadRequests(),
informerFactory.Velero().V1().Restores().Lister(),
informerFactory.Velero().V1().BackupStorageLocations().Lister(),
fakeClient,
informerFactory.Velero().V1().Backups().Lister(),
func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager },
velerotest.NewLogger(),
@ -69,7 +71,7 @@ func newDownloadRequestTestHarness(t *testing.T) *downloadRequestTestHarness {
require.NoError(t, err)
controller.clock = clock.NewFakeClock(clockTime)
controller.newBackupStore = func(*v1.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) {
controller.newBackupStore = func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) {
return backupStore, nil
}
@ -84,34 +86,34 @@ func newDownloadRequestTestHarness(t *testing.T) *downloadRequestTestHarness {
}
}
func newDownloadRequest(phase v1.DownloadRequestPhase, targetKind v1.DownloadTargetKind, targetName string) *v1.DownloadRequest {
return &v1.DownloadRequest{
func newDownloadRequest(phase velerov1api.DownloadRequestPhase, targetKind velerov1api.DownloadTargetKind, targetName string) *velerov1api.DownloadRequest {
return &velerov1api.DownloadRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "a-download-request",
Namespace: v1.DefaultNamespace,
Namespace: velerov1api.DefaultNamespace,
},
Spec: v1.DownloadRequestSpec{
Target: v1.DownloadTarget{
Spec: velerov1api.DownloadRequestSpec{
Target: velerov1api.DownloadTarget{
Kind: targetKind,
Name: targetName,
},
},
Status: v1.DownloadRequestStatus{
Status: velerov1api.DownloadRequestStatus{
Phase: phase,
},
}
}
func newBackupLocation(name, provider, bucket string) *v1.BackupStorageLocation {
return &v1.BackupStorageLocation{
func newBackupLocation(name, provider, bucket string) *velerov1api.BackupStorageLocation {
return &velerov1api.BackupStorageLocation{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: v1.DefaultNamespace,
Namespace: velerov1api.DefaultNamespace,
},
Spec: v1.BackupStorageLocationSpec{
Spec: velerov1api.BackupStorageLocationSpec{
Provider: provider,
StorageType: v1.StorageType{
ObjectStorage: &v1.ObjectStorageLocation{
StorageType: velerov1api.StorageType{
ObjectStorage: &velerov1api.ObjectStorageLocation{
Bucket: bucket,
},
},
@ -120,17 +122,18 @@ func newBackupLocation(name, provider, bucket string) *v1.BackupStorageLocation
}
func TestProcessDownloadRequest(t *testing.T) {
defaultBackup := func() *v1.Backup {
return builder.ForBackup(v1.DefaultNamespace, "a-backup").StorageLocation("a-location").Result()
defaultBackup := func() *velerov1api.Backup {
return builder.ForBackup(velerov1api.DefaultNamespace, "a-backup").StorageLocation("a-location").Result()
}
tests := []struct {
name string
key string
downloadRequest *v1.DownloadRequest
backup *v1.Backup
restore *v1.Restore
backupLocation *v1.BackupStorageLocation
downloadRequest *velerov1api.DownloadRequest
backup *velerov1api.Backup
restore *velerov1api.Restore
backupLocation *velerov1api.BackupStorageLocation
expired bool
expectedErr string
expectGetsURL bool
@ -149,94 +152,94 @@ func TestProcessDownloadRequest(t *testing.T) {
},
{
name: "backup contents request for nonexistent backup returns an error",
downloadRequest: newDownloadRequest("", v1.DownloadTargetKindBackupContents, "a-backup"),
backup: builder.ForBackup(v1.DefaultNamespace, "non-matching-backup").StorageLocation("a-location").Result(),
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindBackupContents, "a-backup"),
backup: builder.ForBackup(velerov1api.DefaultNamespace, "non-matching-backup").StorageLocation("a-location").Result(),
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
expectedErr: "backup.velero.io \"a-backup\" not found",
},
{
name: "restore log request for nonexistent restore returns an error",
downloadRequest: newDownloadRequest("", v1.DownloadTargetKindRestoreLog, "a-backup-20170912150214"),
restore: builder.ForRestore(v1.DefaultNamespace, "non-matching-restore").Phase(v1.RestorePhaseCompleted).Backup("a-backup").Result(),
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindRestoreLog, "a-backup-20170912150214"),
restore: builder.ForRestore(velerov1api.DefaultNamespace, "non-matching-restore").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
backup: defaultBackup(),
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
expectedErr: "error getting Restore: restore.velero.io \"a-backup-20170912150214\" not found",
},
{
name: "backup contents request for backup with nonexistent location returns an error",
downloadRequest: newDownloadRequest("", v1.DownloadTargetKindBackupContents, "a-backup"),
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindBackupContents, "a-backup"),
backup: defaultBackup(),
backupLocation: newBackupLocation("non-matching-location", "a-provider", "a-bucket"),
expectedErr: "backupstoragelocation.velero.io \"a-location\" not found",
expectedErr: "backupstoragelocations.velero.io \"a-location\" not found",
},
{
name: "backup contents request with phase '' gets a url",
downloadRequest: newDownloadRequest("", v1.DownloadTargetKindBackupContents, "a-backup"),
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindBackupContents, "a-backup"),
backup: defaultBackup(),
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
expectGetsURL: true,
},
{
name: "backup contents request with phase 'New' gets a url",
downloadRequest: newDownloadRequest(v1.DownloadRequestPhaseNew, v1.DownloadTargetKindBackupContents, "a-backup"),
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseNew, velerov1api.DownloadTargetKindBackupContents, "a-backup"),
backup: defaultBackup(),
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
expectGetsURL: true,
},
{
name: "backup log request with phase '' gets a url",
downloadRequest: newDownloadRequest("", v1.DownloadTargetKindBackupLog, "a-backup"),
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindBackupLog, "a-backup"),
backup: defaultBackup(),
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
expectGetsURL: true,
},
{
name: "backup log request with phase 'New' gets a url",
downloadRequest: newDownloadRequest(v1.DownloadRequestPhaseNew, v1.DownloadTargetKindBackupLog, "a-backup"),
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseNew, velerov1api.DownloadTargetKindBackupLog, "a-backup"),
backup: defaultBackup(),
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
expectGetsURL: true,
},
{
name: "restore log request with phase '' gets a url",
downloadRequest: newDownloadRequest("", v1.DownloadTargetKindRestoreLog, "a-backup-20170912150214"),
restore: builder.ForRestore(v1.DefaultNamespace, "a-backup-20170912150214").Phase(v1.RestorePhaseCompleted).Backup("a-backup").Result(),
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindRestoreLog, "a-backup-20170912150214"),
restore: builder.ForRestore(velerov1api.DefaultNamespace, "a-backup-20170912150214").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
backup: defaultBackup(),
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
expectGetsURL: true,
},
{
name: "restore log request with phase 'New' gets a url",
downloadRequest: newDownloadRequest(v1.DownloadRequestPhaseNew, v1.DownloadTargetKindRestoreLog, "a-backup-20170912150214"),
restore: builder.ForRestore(v1.DefaultNamespace, "a-backup-20170912150214").Phase(v1.RestorePhaseCompleted).Backup("a-backup").Result(),
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseNew, velerov1api.DownloadTargetKindRestoreLog, "a-backup-20170912150214"),
restore: builder.ForRestore(velerov1api.DefaultNamespace, "a-backup-20170912150214").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
backup: defaultBackup(),
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
expectGetsURL: true,
},
{
name: "restore results request with phase '' gets a url",
downloadRequest: newDownloadRequest("", v1.DownloadTargetKindRestoreResults, "a-backup-20170912150214"),
restore: builder.ForRestore(v1.DefaultNamespace, "a-backup-20170912150214").Phase(v1.RestorePhaseCompleted).Backup("a-backup").Result(),
downloadRequest: newDownloadRequest("", velerov1api.DownloadTargetKindRestoreResults, "a-backup-20170912150214"),
restore: builder.ForRestore(velerov1api.DefaultNamespace, "a-backup-20170912150214").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
backup: defaultBackup(),
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
expectGetsURL: true,
},
{
name: "restore results request with phase 'New' gets a url",
downloadRequest: newDownloadRequest(v1.DownloadRequestPhaseNew, v1.DownloadTargetKindRestoreResults, "a-backup-20170912150214"),
restore: builder.ForRestore(v1.DefaultNamespace, "a-backup-20170912150214").Phase(v1.RestorePhaseCompleted).Backup("a-backup").Result(),
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseNew, velerov1api.DownloadTargetKindRestoreResults, "a-backup-20170912150214"),
restore: builder.ForRestore(velerov1api.DefaultNamespace, "a-backup-20170912150214").Phase(velerov1api.RestorePhaseCompleted).Backup("a-backup").Result(),
backup: defaultBackup(),
backupLocation: newBackupLocation("a-location", "a-provider", "a-bucket"),
expectGetsURL: true,
},
{
name: "request with phase 'Processed' is not deleted if not expired",
downloadRequest: newDownloadRequest(v1.DownloadRequestPhaseProcessed, v1.DownloadTargetKindBackupLog, "a-backup-20170912150214"),
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseProcessed, velerov1api.DownloadTargetKindBackupLog, "a-backup-20170912150214"),
backup: defaultBackup(),
},
{
name: "request with phase 'Processed' is deleted if expired",
downloadRequest: newDownloadRequest(v1.DownloadRequestPhaseProcessed, v1.DownloadTargetKindBackupLog, "a-backup-20170912150214"),
downloadRequest: newDownloadRequest(velerov1api.DownloadRequestPhaseProcessed, velerov1api.DownloadTargetKindBackupLog, "a-backup-20170912150214"),
backup: defaultBackup(),
expired: true,
},
@ -244,13 +247,20 @@ func TestProcessDownloadRequest(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
harness := newDownloadRequestTestHarness(t)
var fakeClient client.Client
if tc.backupLocation != nil {
fakeClient = newFakeClient(t, tc.backupLocation)
} else {
fakeClient = newFakeClient(t)
}
harness := newDownloadRequestTestHarness(t, fakeClient)
// set up test case data
// Set .status.expiration properly for processed requests. Since "expired" is relative to the controller's
// clock time, it's easier to do this here than as part of the test case definitions.
if tc.downloadRequest != nil && tc.downloadRequest.Status.Phase == v1.DownloadRequestPhaseProcessed {
if tc.downloadRequest != nil && tc.downloadRequest.Status.Phase == velerov1api.DownloadRequestPhaseProcessed {
if tc.expired {
tc.downloadRequest.Status.Expiration = &metav1.Time{Time: harness.controller.clock.Now().Add(-1 * time.Minute)}
} else {
@ -273,10 +283,6 @@ func TestProcessDownloadRequest(t *testing.T) {
require.NoError(t, harness.informerFactory.Velero().V1().Backups().Informer().GetStore().Add(tc.backup))
}
if tc.backupLocation != nil {
require.NoError(t, harness.informerFactory.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(tc.backupLocation))
}
if tc.expectGetsURL {
harness.backupStore.On("GetDownloadURL", tc.downloadRequest.Spec.Target).Return("a-url", nil)
}
@ -300,12 +306,12 @@ func TestProcessDownloadRequest(t *testing.T) {
output, err := harness.client.VeleroV1().DownloadRequests(tc.downloadRequest.Namespace).Get(tc.downloadRequest.Name, metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, string(v1.DownloadRequestPhaseProcessed), string(output.Status.Phase))
assert.Equal(t, string(velerov1api.DownloadRequestPhaseProcessed), string(output.Status.Phase))
assert.Equal(t, "a-url", output.Status.DownloadURL)
assert.True(t, velerotest.TimesAreEqual(harness.controller.clock.Now().Add(signedURLTTL), output.Status.Expiration.Time), "expiration does not match")
}
if tc.downloadRequest != nil && tc.downloadRequest.Status.Phase == v1.DownloadRequestPhaseProcessed {
if tc.downloadRequest != nil && tc.downloadRequest.Status.Phase == velerov1api.DownloadRequestPhaseProcessed {
res, err := harness.client.VeleroV1().DownloadRequests(tc.downloadRequest.Namespace).Get(tc.downloadRequest.Name, metav1.GetOptions{})
if tc.expired {

View File

@ -17,6 +17,7 @@ limitations under the License.
package controller
import (
"context"
"time"
"github.com/pkg/errors"
@ -26,6 +27,8 @@ import (
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
@ -45,7 +48,7 @@ type gcController struct {
backupLister velerov1listers.BackupLister
deleteBackupRequestLister velerov1listers.DeleteBackupRequestLister
deleteBackupRequestClient velerov1client.DeleteBackupRequestsGetter
backupLocationLister velerov1listers.BackupStorageLocationLister
kbClient client.Client
clock clock.Clock
}
@ -56,7 +59,7 @@ func NewGCController(
backupInformer velerov1informers.BackupInformer,
deleteBackupRequestLister velerov1listers.DeleteBackupRequestLister,
deleteBackupRequestClient velerov1client.DeleteBackupRequestsGetter,
backupLocationLister velerov1listers.BackupStorageLocationLister,
kbClient client.Client,
) Interface {
c := &gcController{
genericController: newGenericController("gc-controller", logger),
@ -64,7 +67,7 @@ func NewGCController(
backupLister: backupInformer.Lister(),
deleteBackupRequestLister: deleteBackupRequestLister,
deleteBackupRequestClient: deleteBackupRequestClient,
backupLocationLister: backupLocationLister,
kbClient: kbClient,
}
c.syncHandler = c.processQueueItem
@ -130,11 +133,14 @@ func (c *gcController) processQueueItem(key string) error {
log.Info("Backup has expired")
loc, err := c.backupLocationLister.BackupStorageLocations(ns).Get(backup.Spec.StorageLocation)
if apierrors.IsNotFound(err) {
log.Warnf("Backup cannot be garbage-collected because backup storage location %s does not exist", backup.Spec.StorageLocation)
}
if err != nil {
loc := &velerov1api.BackupStorageLocation{}
if err := c.kbClient.Get(context.Background(), client.ObjectKey{
Namespace: ns,
Name: backup.Spec.StorageLocation,
}, loc); err != nil {
if apierrors.IsNotFound(err) {
log.Warnf("Backup cannot be garbage-collected because backup storage location %s does not exist", backup.Spec.StorageLocation)
}
return errors.Wrap(err, "error getting backup storage location")
}

View File

@ -32,7 +32,9 @@ import (
"k8s.io/apimachinery/pkg/watch"
core "k8s.io/client-go/testing"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
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/fake"
informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions"
@ -50,7 +52,7 @@ func TestGCControllerEnqueueAllBackups(t *testing.T) {
sharedInformers.Velero().V1().Backups(),
sharedInformers.Velero().V1().DeleteBackupRequests().Lister(),
client.VeleroV1(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
nil,
).(*gcController)
)
@ -64,7 +66,7 @@ func TestGCControllerEnqueueAllBackups(t *testing.T) {
var expected []string
for i := 0; i < 3; i++ {
backup := builder.ForBackup(api.DefaultNamespace, fmt.Sprintf("backup-%d", i)).Result()
backup := builder.ForBackup(velerov1api.DefaultNamespace, fmt.Sprintf("backup-%d", i)).Result()
sharedInformers.Velero().V1().Backups().Informer().GetStore().Add(backup)
expected = append(expected, kube.NamespaceAndName(backup))
}
@ -111,7 +113,7 @@ func TestGCControllerHasUpdateFunc(t *testing.T) {
sharedInformers.Velero().V1().Backups(),
sharedInformers.Velero().V1().DeleteBackupRequests().Lister(),
client.VeleroV1(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
nil,
).(*gcController)
keys := make(chan string)
@ -148,14 +150,15 @@ func TestGCControllerHasUpdateFunc(t *testing.T) {
}
func TestGCControllerProcessQueueItem(t *testing.T) {
fakeClock := clock.NewFakeClock(time.Now())
defaultBackupLocation := builder.ForBackupStorageLocation("velero", "default").Result()
tests := []struct {
name string
backup *api.Backup
deleteBackupRequests []*api.DeleteBackupRequest
backupLocation *api.BackupStorageLocation
backup *velerov1api.Backup
deleteBackupRequests []*velerov1api.DeleteBackupRequest
backupLocation *velerov1api.BackupStorageLocation
expectDeletion bool
createDeleteBackupRequestError bool
expectError bool
@ -172,13 +175,13 @@ func TestGCControllerProcessQueueItem(t *testing.T) {
{
name: "expired backup in read-only storage location is not deleted",
backup: defaultBackup().Expiration(fakeClock.Now().Add(-time.Minute)).StorageLocation("read-only").Result(),
backupLocation: builder.ForBackupStorageLocation("velero", "read-only").AccessMode(api.BackupStorageLocationAccessModeReadOnly).Result(),
backupLocation: builder.ForBackupStorageLocation("velero", "read-only").AccessMode(velerov1api.BackupStorageLocationAccessModeReadOnly).Result(),
expectDeletion: false,
},
{
name: "expired backup in read-write storage location is deleted",
backup: defaultBackup().Expiration(fakeClock.Now().Add(-time.Minute)).StorageLocation("read-write").Result(),
backupLocation: builder.ForBackupStorageLocation("velero", "read-write").AccessMode(api.BackupStorageLocationAccessModeReadWrite).Result(),
backupLocation: builder.ForBackupStorageLocation("velero", "read-write").AccessMode(velerov1api.BackupStorageLocationAccessModeReadWrite).Result(),
expectDeletion: true,
},
{
@ -191,18 +194,18 @@ func TestGCControllerProcessQueueItem(t *testing.T) {
name: "expired backup with a pending deletion request is not deleted",
backup: defaultBackup().Expiration(fakeClock.Now().Add(-time.Second)).StorageLocation("default").Result(),
backupLocation: defaultBackupLocation,
deleteBackupRequests: []*api.DeleteBackupRequest{
deleteBackupRequests: []*velerov1api.DeleteBackupRequest{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: api.DefaultNamespace,
Namespace: velerov1api.DefaultNamespace,
Name: "foo",
Labels: map[string]string{
api.BackupNameLabel: "backup-1",
api.BackupUIDLabel: "",
velerov1api.BackupNameLabel: "backup-1",
velerov1api.BackupUIDLabel: "",
},
},
Status: api.DeleteBackupRequestStatus{
Phase: api.DeleteBackupRequestPhaseInProgress,
Status: velerov1api.DeleteBackupRequestStatus{
Phase: velerov1api.DeleteBackupRequestPhaseInProgress,
},
},
},
@ -212,18 +215,18 @@ func TestGCControllerProcessQueueItem(t *testing.T) {
name: "expired backup with only processed deletion requests is deleted",
backup: defaultBackup().Expiration(fakeClock.Now().Add(-time.Second)).StorageLocation("default").Result(),
backupLocation: defaultBackupLocation,
deleteBackupRequests: []*api.DeleteBackupRequest{
deleteBackupRequests: []*velerov1api.DeleteBackupRequest{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: api.DefaultNamespace,
Namespace: velerov1api.DefaultNamespace,
Name: "foo",
Labels: map[string]string{
api.BackupNameLabel: "backup-1",
api.BackupUIDLabel: "",
velerov1api.BackupNameLabel: "backup-1",
velerov1api.BackupUIDLabel: "",
},
},
Status: api.DeleteBackupRequestStatus{
Phase: api.DeleteBackupRequestPhaseProcessed,
Status: velerov1api.DeleteBackupRequestStatus{
Phase: velerov1api.DeleteBackupRequestPhaseProcessed,
},
},
},
@ -246,12 +249,19 @@ func TestGCControllerProcessQueueItem(t *testing.T) {
sharedInformers = informers.NewSharedInformerFactory(client, 0)
)
var fakeClient kbclient.Client
if test.backupLocation != nil {
fakeClient = newFakeClient(t, test.backupLocation)
} else {
fakeClient = newFakeClient(t)
}
controller := NewGCController(
velerotest.NewLogger(),
sharedInformers.Velero().V1().Backups(),
sharedInformers.Velero().V1().DeleteBackupRequests().Lister(),
client.VeleroV1(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
fakeClient,
).(*gcController)
controller.clock = fakeClock
@ -261,10 +271,6 @@ func TestGCControllerProcessQueueItem(t *testing.T) {
sharedInformers.Velero().V1().Backups().Informer().GetStore().Add(test.backup)
}
if test.backupLocation != nil {
sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(test.backupLocation)
}
for _, dbr := range test.deleteBackupRequests {
sharedInformers.Velero().V1().DeleteBackupRequests().Informer().GetStore().Add(dbr)
}

View File

@ -42,6 +42,8 @@ import (
"github.com/vmware-tanzu/velero/pkg/restic"
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
"github.com/vmware-tanzu/velero/pkg/util/kube"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type podVolumeBackupController struct {
@ -53,7 +55,7 @@ type podVolumeBackupController struct {
podLister corev1listers.PodLister
pvcLister corev1listers.PersistentVolumeClaimLister
pvLister corev1listers.PersistentVolumeLister
backupLocationLister listers.BackupStorageLocationLister
kbClient client.Client
nodeName string
processBackupFunc func(*velerov1api.PodVolumeBackup) error
@ -70,7 +72,7 @@ func NewPodVolumeBackupController(
secretInformer cache.SharedIndexInformer,
pvcInformer corev1informers.PersistentVolumeClaimInformer,
pvInformer corev1informers.PersistentVolumeInformer,
backupLocationInformer informers.BackupStorageLocationInformer,
kbClient client.Client,
nodeName string,
) Interface {
c := &podVolumeBackupController{
@ -81,7 +83,7 @@ func NewPodVolumeBackupController(
secretLister: corev1listers.NewSecretLister(secretInformer.GetIndexer()),
pvcLister: pvcInformer.Lister(),
pvLister: pvInformer.Lister(),
backupLocationLister: backupLocationInformer.Lister(),
kbClient: kbClient,
nodeName: nodeName,
fileSystem: filesystem.NewFileSystem(),
@ -95,7 +97,6 @@ func NewPodVolumeBackupController(
podInformer.HasSynced,
secretInformer.HasSynced,
pvcInformer.Informer().HasSynced,
backupLocationInformer.Informer().HasSynced,
)
c.processBackupFunc = c.processBackup
@ -228,10 +229,11 @@ func (c *podVolumeBackupController) processBackup(req *velerov1api.PodVolumeBack
)
// if there's a caCert on the ObjectStorage, write it to disk so that it can be passed to restic
caCert, err := restic.GetCACert(c.backupLocationLister, req.Namespace, req.Spec.BackupStorageLocation)
caCert, err := restic.GetCACert(c.kbClient, req.Namespace, req.Spec.BackupStorageLocation)
if err != nil {
log.WithError(err).Error("Error getting caCert")
}
var caCertFile string
if caCert != nil {
caCertFile, err = restic.TempCACertFile(caCert, req.Spec.BackupStorageLocation, c.fileSystem)
@ -247,12 +249,12 @@ func (c *podVolumeBackupController) processBackup(req *velerov1api.PodVolumeBack
// set resticCmd.Env appropriately (currently for Azure and S3 based backuplocations)
var env []string
if strings.HasPrefix(req.Spec.RepoIdentifier, "azure") {
if env, err = restic.AzureCmdEnv(c.backupLocationLister, req.Namespace, req.Spec.BackupStorageLocation); err != nil {
if env, err = restic.AzureCmdEnv(c.kbClient, req.Namespace, req.Spec.BackupStorageLocation); err != nil {
return c.fail(req, errors.Wrap(err, "error setting restic cmd env").Error(), log)
}
resticCmd.Env = env
} else if strings.HasPrefix(req.Spec.RepoIdentifier, "s3") {
if env, err = restic.S3CmdEnv(c.backupLocationLister, req.Namespace, req.Spec.BackupStorageLocation); err != nil {
if env, err = restic.S3CmdEnv(c.kbClient, req.Namespace, req.Spec.BackupStorageLocation); err != nil {
return c.fail(req, errors.Wrap(err, "error setting restic cmd env").Error(), log)
}
resticCmd.Env = env

View File

@ -36,6 +36,8 @@ import (
corev1informers "k8s.io/client-go/informers/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
k8scache "sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
@ -56,7 +58,8 @@ type podVolumeRestoreController struct {
secretLister corev1listers.SecretLister
pvcLister corev1listers.PersistentVolumeClaimLister
pvLister corev1listers.PersistentVolumeLister
backupLocationLister listers.BackupStorageLocationLister
backupLocationInformer k8scache.Informer
kbClient client.Client
nodeName string
processRestoreFunc func(*velerov1api.PodVolumeRestore) error
@ -73,7 +76,7 @@ func NewPodVolumeRestoreController(
secretInformer cache.SharedIndexInformer,
pvcInformer corev1informers.PersistentVolumeClaimInformer,
pvInformer corev1informers.PersistentVolumeInformer,
backupLocationInformer informers.BackupStorageLocationInformer,
kbClient client.Client,
nodeName string,
) Interface {
c := &podVolumeRestoreController{
@ -84,7 +87,7 @@ func NewPodVolumeRestoreController(
secretLister: corev1listers.NewSecretLister(secretInformer.GetIndexer()),
pvcLister: pvcInformer.Lister(),
pvLister: pvInformer.Lister(),
backupLocationLister: backupLocationInformer.Lister(),
kbClient: kbClient,
nodeName: nodeName,
fileSystem: filesystem.NewFileSystem(),
@ -98,7 +101,6 @@ func NewPodVolumeRestoreController(
podInformer.HasSynced,
secretInformer.HasSynced,
pvcInformer.Informer().HasSynced,
backupLocationInformer.Informer().HasSynced,
)
c.processRestoreFunc = c.processRestore
@ -294,10 +296,11 @@ func (c *podVolumeRestoreController) processRestore(req *velerov1api.PodVolumeRe
defer os.Remove(credsFile)
// if there's a caCert on the ObjectStorage, write it to disk so that it can be passed to restic
caCert, err := restic.GetCACert(c.backupLocationLister, req.Namespace, req.Spec.BackupStorageLocation)
caCert, err := restic.GetCACert(c.kbClient, req.Namespace, req.Spec.BackupStorageLocation)
if err != nil {
log.WithError(err).Error("Error getting caCert")
}
var caCertFile string
if caCert != nil {
caCertFile, err = restic.TempCACertFile(caCert, req.Spec.BackupStorageLocation, c.fileSystem)
@ -347,13 +350,13 @@ func (c *podVolumeRestoreController) restorePodVolume(req *velerov1api.PodVolume
// Running restic command might need additional provider specific environment variables. Based on the provider, we
// set resticCmd.Env appropriately (currently for Azure and S3 based backuplocations)
if strings.HasPrefix(req.Spec.RepoIdentifier, "azure") {
env, err := restic.AzureCmdEnv(c.backupLocationLister, req.Namespace, req.Spec.BackupStorageLocation)
env, err := restic.AzureCmdEnv(c.kbClient, req.Namespace, req.Spec.BackupStorageLocation)
if err != nil {
return c.failRestore(req, errors.Wrap(err, "error setting restic cmd env").Error(), log)
}
resticCmd.Env = env
} else if strings.HasPrefix(req.Spec.RepoIdentifier, "s3") {
env, err := restic.S3CmdEnv(c.backupLocationLister, req.Namespace, req.Spec.BackupStorageLocation)
env, err := restic.S3CmdEnv(c.kbClient, req.Namespace, req.Spec.BackupStorageLocation)
if err != nil {
return c.failRestore(req, errors.Wrap(err, "error setting restic cmd env").Error(), log)
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package controller
import (
"context"
"encoding/json"
"strings"
"time"
@ -31,11 +32,13 @@ import (
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/client-go/tools/cache"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
velerov1informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions/velero/v1"
velerov1listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1"
"github.com/vmware-tanzu/velero/pkg/restic"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type resticRepositoryController struct {
@ -43,7 +46,7 @@ type resticRepositoryController struct {
resticRepositoryClient velerov1client.ResticRepositoriesGetter
resticRepositoryLister velerov1listers.ResticRepositoryLister
backupLocationLister velerov1listers.BackupStorageLocationLister
kbClient client.Client
repositoryManager restic.RepositoryManager
defaultMaintenanceFrequency time.Duration
@ -55,7 +58,7 @@ func NewResticRepositoryController(
logger logrus.FieldLogger,
resticRepositoryInformer velerov1informers.ResticRepositoryInformer,
resticRepositoryClient velerov1client.ResticRepositoriesGetter,
backupLocationLister velerov1listers.BackupStorageLocationLister,
kbClient client.Client,
repositoryManager restic.RepositoryManager,
defaultMaintenanceFrequency time.Duration,
) Interface {
@ -63,7 +66,7 @@ func NewResticRepositoryController(
genericController: newGenericController("restic-repository", logger),
resticRepositoryClient: resticRepositoryClient,
resticRepositoryLister: resticRepositoryInformer.Lister(),
backupLocationLister: backupLocationLister,
kbClient: kbClient,
repositoryManager: repositoryManager,
defaultMaintenanceFrequency: defaultMaintenanceFrequency,
@ -129,7 +132,7 @@ func (c *resticRepositoryController) processQueueItem(key string) error {
// Don't mutate the shared cache
reqCopy := req.DeepCopy()
if req.Status.Phase == "" || req.Status.Phase == v1.ResticRepositoryPhaseNew {
if req.Status.Phase == "" || req.Status.Phase == velerov1api.ResticRepositoryPhaseNew {
return c.initializeRepo(reqCopy, log)
}
@ -142,29 +145,32 @@ func (c *resticRepositoryController) processQueueItem(key string) error {
}
switch req.Status.Phase {
case v1.ResticRepositoryPhaseReady:
case velerov1api.ResticRepositoryPhaseReady:
return c.runMaintenanceIfDue(reqCopy, log)
case v1.ResticRepositoryPhaseNotReady:
case velerov1api.ResticRepositoryPhaseNotReady:
return c.checkNotReadyRepo(reqCopy, log)
}
return nil
}
func (c *resticRepositoryController) initializeRepo(req *v1.ResticRepository, log logrus.FieldLogger) error {
func (c *resticRepositoryController) initializeRepo(req *velerov1api.ResticRepository, log logrus.FieldLogger) error {
log.Info("Initializing restic repository")
// confirm the repo's BackupStorageLocation is valid
loc, err := c.backupLocationLister.BackupStorageLocations(req.Namespace).Get(req.Spec.BackupStorageLocation)
if err != nil {
loc := &velerov1api.BackupStorageLocation{}
if err := c.kbClient.Get(context.Background(), client.ObjectKey{
Namespace: req.Namespace,
Name: req.Spec.BackupStorageLocation,
}, loc); err != nil {
return c.patchResticRepository(req, repoNotReady(err.Error()))
}
repoIdentifier, err := restic.GetRepoIdentifier(loc, req.Spec.VolumeNamespace)
if err != nil {
return c.patchResticRepository(req, func(r *v1.ResticRepository) {
return c.patchResticRepository(req, func(r *velerov1api.ResticRepository) {
r.Status.Message = err.Error()
r.Status.Phase = v1.ResticRepositoryPhaseNotReady
r.Status.Phase = velerov1api.ResticRepositoryPhaseNotReady
if r.Spec.MaintenanceFrequency.Duration <= 0 {
r.Spec.MaintenanceFrequency = metav1.Duration{Duration: c.defaultMaintenanceFrequency}
@ -173,7 +179,7 @@ func (c *resticRepositoryController) initializeRepo(req *v1.ResticRepository, lo
}
// defaulting - if the patch fails, return an error so the item is returned to the queue
if err := c.patchResticRepository(req, func(r *v1.ResticRepository) {
if err := c.patchResticRepository(req, func(r *velerov1api.ResticRepository) {
r.Spec.ResticIdentifier = repoIdentifier
if r.Spec.MaintenanceFrequency.Duration <= 0 {
@ -187,8 +193,8 @@ func (c *resticRepositoryController) initializeRepo(req *v1.ResticRepository, lo
return c.patchResticRepository(req, repoNotReady(err.Error()))
}
return c.patchResticRepository(req, func(req *v1.ResticRepository) {
req.Status.Phase = v1.ResticRepositoryPhaseReady
return c.patchResticRepository(req, func(req *velerov1api.ResticRepository) {
req.Status.Phase = velerov1api.ResticRepositoryPhaseReady
req.Status.LastMaintenanceTime = &metav1.Time{Time: time.Now()}
})
}
@ -196,7 +202,7 @@ func (c *resticRepositoryController) initializeRepo(req *v1.ResticRepository, lo
// ensureRepo checks to see if a repository exists, and attempts to initialize it if
// it does not exist. An error is returned if the repository can't be connected to
// or initialized.
func ensureRepo(repo *v1.ResticRepository, repoManager restic.RepositoryManager) error {
func ensureRepo(repo *velerov1api.ResticRepository, repoManager restic.RepositoryManager) error {
if err := repoManager.ConnectToRepo(repo); err != nil {
// If the repository has not yet been initialized, the error message will always include
// the following string. This is the only scenario where we should try to initialize it.
@ -212,7 +218,7 @@ func ensureRepo(repo *v1.ResticRepository, repoManager restic.RepositoryManager)
return nil
}
func (c *resticRepositoryController) runMaintenanceIfDue(req *v1.ResticRepository, log logrus.FieldLogger) error {
func (c *resticRepositoryController) runMaintenanceIfDue(req *velerov1api.ResticRepository, log logrus.FieldLogger) error {
log.Debug("resticRepositoryController.runMaintenanceIfDue")
now := c.clock.Now()
@ -229,23 +235,23 @@ func (c *resticRepositoryController) runMaintenanceIfDue(req *v1.ResticRepositor
log.Debug("Pruning repo")
if err := c.repositoryManager.PruneRepo(req); err != nil {
log.WithError(err).Warn("error pruning repository")
if patchErr := c.patchResticRepository(req, func(r *v1.ResticRepository) {
if patchErr := c.patchResticRepository(req, func(r *velerov1api.ResticRepository) {
r.Status.Message = err.Error()
}); patchErr != nil {
return patchErr
}
}
return c.patchResticRepository(req, func(req *v1.ResticRepository) {
return c.patchResticRepository(req, func(req *velerov1api.ResticRepository) {
req.Status.LastMaintenanceTime = &metav1.Time{Time: now}
})
}
func dueForMaintenance(req *v1.ResticRepository, now time.Time) bool {
func dueForMaintenance(req *velerov1api.ResticRepository, now time.Time) bool {
return req.Status.LastMaintenanceTime == nil || req.Status.LastMaintenanceTime.Add(req.Spec.MaintenanceFrequency.Duration).Before(now)
}
func (c *resticRepositoryController) checkNotReadyRepo(req *v1.ResticRepository, log logrus.FieldLogger) error {
func (c *resticRepositoryController) checkNotReadyRepo(req *velerov1api.ResticRepository, log logrus.FieldLogger) error {
// no identifier: can't possibly be ready, so just return
if req.Spec.ResticIdentifier == "" {
return nil
@ -262,16 +268,16 @@ func (c *resticRepositoryController) checkNotReadyRepo(req *v1.ResticRepository,
return c.patchResticRepository(req, repoReady())
}
func repoNotReady(msg string) func(*v1.ResticRepository) {
return func(r *v1.ResticRepository) {
r.Status.Phase = v1.ResticRepositoryPhaseNotReady
func repoNotReady(msg string) func(*velerov1api.ResticRepository) {
return func(r *velerov1api.ResticRepository) {
r.Status.Phase = velerov1api.ResticRepositoryPhaseNotReady
r.Status.Message = msg
}
}
func repoReady() func(*v1.ResticRepository) {
return func(r *v1.ResticRepository) {
r.Status.Phase = v1.ResticRepositoryPhaseReady
func repoReady() func(*velerov1api.ResticRepository) {
return func(r *velerov1api.ResticRepository) {
r.Status.Phase = velerov1api.ResticRepositoryPhaseReady
r.Status.Message = ""
}
}
@ -279,7 +285,7 @@ func repoReady() func(*v1.ResticRepository) {
// patchResticRepository mutates req with the provided mutate function, and patches it
// through the Kube API. After executing this function, req will be updated with both
// the mutation and the results of the Patch() API call.
func (c *resticRepositoryController) patchResticRepository(req *v1.ResticRepository, mutate func(*v1.ResticRepository)) error {
func (c *resticRepositoryController) patchResticRepository(req *velerov1api.ResticRepository, mutate func(*velerov1api.ResticRepository)) error {
// Record original json
oldData, err := json.Marshal(req)
if err != nil {
@ -305,7 +311,7 @@ func (c *resticRepositoryController) patchResticRepository(req *v1.ResticReposit
}
// patch, and if successful, update req
var patched *v1.ResticRepository
var patched *velerov1api.ResticRepository
if patched, err = c.resticRepositoryClient.ResticRepositories(req.Namespace).Patch(req.Name, types.MergePatchType, patchBytes); err != nil {
return errors.Wrap(err, "error patching ResticRepository")
}

View File

@ -19,6 +19,7 @@ package controller
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
@ -48,6 +49,8 @@ import (
"github.com/vmware-tanzu/velero/pkg/util/collections"
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
"github.com/vmware-tanzu/velero/pkg/util/logging"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// nonRestorableResources is a blacklist for the restoration process. Any resources
@ -80,7 +83,7 @@ type restoreController struct {
restorer pkgrestore.Restorer
backupLister velerov1listers.BackupLister
restoreLister velerov1listers.RestoreLister
backupLocationLister velerov1listers.BackupStorageLocationLister
kbClient client.Client
snapshotLocationLister velerov1listers.VolumeSnapshotLocationLister
restoreLogLevel logrus.Level
defaultBackupLocation string
@ -88,7 +91,7 @@ type restoreController struct {
logFormat logging.Format
newPluginManager func(logger logrus.FieldLogger) clientmgmt.Manager
newBackupStore func(*api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error)
newBackupStore func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error)
}
func NewRestoreController(
@ -98,7 +101,7 @@ func NewRestoreController(
podVolumeBackupClient velerov1client.PodVolumeBackupsGetter,
restorer pkgrestore.Restorer,
backupLister velerov1listers.BackupLister,
backupLocationLister velerov1listers.BackupStorageLocationLister,
kbClient client.Client,
snapshotLocationLister velerov1listers.VolumeSnapshotLocationLister,
logger logrus.FieldLogger,
restoreLogLevel logrus.Level,
@ -115,7 +118,7 @@ func NewRestoreController(
restorer: restorer,
backupLister: backupLister,
restoreLister: restoreInformer.Lister(),
backupLocationLister: backupLocationLister,
kbClient: kbClient,
snapshotLocationLister: snapshotLocationLister,
restoreLogLevel: restoreLogLevel,
defaultBackupLocation: defaultBackupLocation,
@ -274,7 +277,7 @@ func (c *restoreController) processRestore(restore *api.Restore) error {
type backupInfo struct {
backup *api.Backup
location *api.BackupStorageLocation
location *velerov1api.BackupStorageLocation
backupStore persistence.BackupStore
}
@ -396,8 +399,11 @@ func (c *restoreController) fetchBackupInfo(backupName string, pluginManager cli
return backupInfo{}, err
}
location, err := c.backupLocationLister.BackupStorageLocations(c.namespace).Get(backup.Spec.StorageLocation)
if err != nil {
location := &velerov1api.BackupStorageLocation{}
if err := c.kbClient.Get(context.Background(), client.ObjectKey{
Namespace: c.namespace,
Name: backup.Spec.StorageLocation,
}, location); err != nil {
return backupInfo{}, errors.WithStack(err)
}

View File

@ -18,6 +18,7 @@ package controller
import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"testing"
@ -34,7 +35,7 @@ import (
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
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/fake"
informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions"
@ -52,29 +53,30 @@ import (
)
func TestFetchBackupInfo(t *testing.T) {
tests := []struct {
name string
backupName string
informerLocations []*api.BackupStorageLocation
informerBackups []*api.Backup
backupStoreBackup *api.Backup
informerLocations []*velerov1api.BackupStorageLocation
informerBackups []*velerov1api.Backup
backupStoreBackup *velerov1api.Backup
backupStoreError error
expectedRes *api.Backup
expectedRes *velerov1api.Backup
expectedErr bool
}{
{
name: "lister has backup",
backupName: "backup-1",
informerLocations: []*api.BackupStorageLocation{builder.ForBackupStorageLocation("velero", "default").Provider("myCloud").Bucket("bucket").Result()},
informerBackups: []*api.Backup{defaultBackup().StorageLocation("default").Result()},
informerLocations: []*velerov1api.BackupStorageLocation{builder.ForBackupStorageLocation("velero", "default").Provider("myCloud").Bucket("bucket").Result()},
informerBackups: []*velerov1api.Backup{defaultBackup().StorageLocation("default").Result()},
expectedRes: defaultBackup().StorageLocation("default").Result(),
},
{
name: "lister does not have a backup, but backupSvc does",
backupName: "backup-1",
backupStoreBackup: defaultBackup().StorageLocation("default").Result(),
informerLocations: []*api.BackupStorageLocation{builder.ForBackupStorageLocation("velero", "default").Provider("myCloud").Bucket("bucket").Result()},
informerBackups: []*api.Backup{defaultBackup().StorageLocation("default").Result()},
informerLocations: []*velerov1api.BackupStorageLocation{builder.ForBackupStorageLocation("velero", "default").Provider("myCloud").Bucket("bucket").Result()},
informerBackups: []*velerov1api.Backup{defaultBackup().StorageLocation("default").Result()},
expectedRes: defaultBackup().StorageLocation("default").Result(),
},
{
@ -91,6 +93,7 @@ func TestFetchBackupInfo(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var (
client = fake.NewSimpleClientset()
fakeClient = newFakeClient(t)
restorer = &fakeRestorer{}
sharedInformers = informers.NewSharedInformerFactory(client, 0)
logger = velerotest.NewLogger()
@ -102,13 +105,13 @@ func TestFetchBackupInfo(t *testing.T) {
defer backupStore.AssertExpectations(t)
c := NewRestoreController(
api.DefaultNamespace,
velerov1api.DefaultNamespace,
sharedInformers.Velero().V1().Restores(),
client.VeleroV1(),
client.VeleroV1(),
restorer,
sharedInformers.Velero().V1().Backups().Lister(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
fakeClient,
sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
logger,
logrus.InfoLevel,
@ -118,13 +121,13 @@ func TestFetchBackupInfo(t *testing.T) {
formatFlag,
).(*restoreController)
c.newBackupStore = func(*api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) {
c.newBackupStore = func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) {
return backupStore, nil
}
if test.backupStoreError == nil {
for _, itm := range test.informerLocations {
sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(itm)
require.NoError(t, fakeClient.Create(context.Background(), itm))
}
for _, itm := range test.informerBackups {
@ -157,7 +160,7 @@ func TestProcessQueueItemSkips(t *testing.T) {
tests := []struct {
name string
restoreKey string
restore *api.Restore
restore *velerov1api.Restore
expectError bool
}{
{
@ -172,17 +175,17 @@ func TestProcessQueueItemSkips(t *testing.T) {
{
name: "restore with phase InProgress does not get processed",
restoreKey: "foo/bar",
restore: builder.ForRestore("foo", "bar").Phase(api.RestorePhaseInProgress).Result(),
restore: builder.ForRestore("foo", "bar").Phase(velerov1api.RestorePhaseInProgress).Result(),
},
{
name: "restore with phase Completed does not get processed",
restoreKey: "foo/bar",
restore: builder.ForRestore("foo", "bar").Phase(api.RestorePhaseCompleted).Result(),
restore: builder.ForRestore("foo", "bar").Phase(velerov1api.RestorePhaseCompleted).Result(),
},
{
name: "restore with phase FailedValidation does not get processed",
restoreKey: "foo/bar",
restore: builder.ForRestore("foo", "bar").Phase(api.RestorePhaseFailedValidation).Result(),
restore: builder.ForRestore("foo", "bar").Phase(velerov1api.RestorePhaseFailedValidation).Result(),
},
}
@ -198,13 +201,13 @@ func TestProcessQueueItemSkips(t *testing.T) {
)
c := NewRestoreController(
api.DefaultNamespace,
velerov1api.DefaultNamespace,
sharedInformers.Velero().V1().Restores(),
client.VeleroV1(),
client.VeleroV1(),
restorer,
sharedInformers.Velero().V1().Backups().Lister(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
nil,
sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
logger,
logrus.InfoLevel,
@ -226,20 +229,21 @@ func TestProcessQueueItemSkips(t *testing.T) {
}
func TestProcessQueueItem(t *testing.T) {
defaultStorageLocation := builder.ForBackupStorageLocation("velero", "default").Provider("myCloud").Bucket("bucket").Result()
tests := []struct {
name string
restoreKey string
location *api.BackupStorageLocation
restore *api.Restore
backup *api.Backup
location *velerov1api.BackupStorageLocation
restore *velerov1api.Restore
backup *velerov1api.Backup
restorerError error
expectedErr bool
expectedPhase string
expectedValidationErrors []string
expectedRestoreErrors int
expectedRestorerCall *api.Restore
expectedRestorerCall *velerov1api.Restore
backupStoreGetBackupMetadataErr error
backupStoreGetBackupContentsErr error
putRestoreLogErr error
@ -248,80 +252,80 @@ func TestProcessQueueItem(t *testing.T) {
{
name: "restore with both namespace in both includedNamespaces and excludedNamespaces fails validation",
location: defaultStorageLocation,
restore: NewRestore("foo", "bar", "backup-1", "another-1", "*", api.RestorePhaseNew).ExcludedNamespaces("another-1").Result(),
restore: NewRestore("foo", "bar", "backup-1", "another-1", "*", velerov1api.RestorePhaseNew).ExcludedNamespaces("another-1").Result(),
backup: defaultBackup().StorageLocation("default").Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseFailedValidation),
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
expectedValidationErrors: []string{"Invalid included/excluded namespace lists: excludes list cannot contain an item in the includes list: another-1"},
},
{
name: "restore with resource in both includedResources and excludedResources fails validation",
location: defaultStorageLocation,
restore: NewRestore("foo", "bar", "backup-1", "*", "a-resource", api.RestorePhaseNew).ExcludedResources("a-resource").Result(),
restore: NewRestore("foo", "bar", "backup-1", "*", "a-resource", velerov1api.RestorePhaseNew).ExcludedResources("a-resource").Result(),
backup: defaultBackup().StorageLocation("default").Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseFailedValidation),
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
expectedValidationErrors: []string{"Invalid included/excluded resource lists: excludes list cannot contain an item in the includes list: a-resource"},
},
{
name: "new restore with empty backup and schedule names fails validation",
restore: NewRestore("foo", "bar", "", "ns-1", "", api.RestorePhaseNew).Result(),
restore: NewRestore("foo", "bar", "", "ns-1", "", velerov1api.RestorePhaseNew).Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseFailedValidation),
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
expectedValidationErrors: []string{"Either a backup or schedule must be specified as a source for the restore, but not both"},
},
{
name: "new restore with backup and schedule names provided fails validation",
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", api.RestorePhaseNew).Schedule("sched-1").Result(),
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Schedule("sched-1").Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseFailedValidation),
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
expectedValidationErrors: []string{"Either a backup or schedule must be specified as a source for the restore, but not both"},
},
{
name: "valid restore with schedule name gets executed",
location: defaultStorageLocation,
restore: NewRestore("foo", "bar", "", "ns-1", "", api.RestorePhaseNew).Schedule("sched-1").Result(),
backup: defaultBackup().StorageLocation("default").ObjectMeta(builder.WithLabels(api.ScheduleNameLabel, "sched-1")).Phase(api.BackupPhaseCompleted).Result(),
restore: NewRestore("foo", "bar", "", "ns-1", "", velerov1api.RestorePhaseNew).Schedule("sched-1").Result(),
backup: defaultBackup().StorageLocation("default").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "sched-1")).Phase(velerov1api.BackupPhaseCompleted).Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseInProgress),
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", api.RestorePhaseInProgress).Schedule("sched-1").Result(),
expectedPhase: string(velerov1api.RestorePhaseInProgress),
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Schedule("sched-1").Result(),
},
{
name: "restore with non-existent backup name fails",
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "*", api.RestorePhaseNew).Result(),
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "*", velerov1api.RestorePhaseNew).Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseFailedValidation),
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
expectedValidationErrors: []string{"Error retrieving backup: backup.velero.io \"backup-1\" not found"},
backupStoreGetBackupMetadataErr: errors.New("no backup here"),
},
{
name: "restorer throwing an error causes the restore to fail",
location: defaultStorageLocation,
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", api.RestorePhaseNew).Result(),
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Result(),
backup: defaultBackup().StorageLocation("default").Result(),
restorerError: errors.New("blarg"),
expectedErr: false,
expectedPhase: string(api.RestorePhaseInProgress),
expectedFinalPhase: string(api.RestorePhasePartiallyFailed),
expectedPhase: string(velerov1api.RestorePhaseInProgress),
expectedFinalPhase: string(velerov1api.RestorePhasePartiallyFailed),
expectedRestoreErrors: 1,
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", api.RestorePhaseInProgress).Result(),
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Result(),
},
{
name: "valid restore gets executed",
location: defaultStorageLocation,
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", api.RestorePhaseNew).Result(),
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Result(),
backup: defaultBackup().StorageLocation("default").Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseInProgress),
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", api.RestorePhaseInProgress).Result(),
expectedPhase: string(velerov1api.RestorePhaseInProgress),
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Result(),
},
{
name: "restoration of nodes is not supported",
location: defaultStorageLocation,
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "nodes", api.RestorePhaseNew).Result(),
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "nodes", velerov1api.RestorePhaseNew).Result(),
backup: defaultBackup().StorageLocation("default").Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseFailedValidation),
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
expectedValidationErrors: []string{
"nodes are non-restorable resources",
"Invalid included/excluded resource lists: excludes list cannot contain an item in the includes list: nodes",
@ -330,10 +334,10 @@ func TestProcessQueueItem(t *testing.T) {
{
name: "restoration of events is not supported",
location: defaultStorageLocation,
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "events", api.RestorePhaseNew).Result(),
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "events", velerov1api.RestorePhaseNew).Result(),
backup: defaultBackup().StorageLocation("default").Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseFailedValidation),
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
expectedValidationErrors: []string{
"events are non-restorable resources",
"Invalid included/excluded resource lists: excludes list cannot contain an item in the includes list: events",
@ -342,10 +346,10 @@ func TestProcessQueueItem(t *testing.T) {
{
name: "restoration of events.events.k8s.io is not supported",
location: defaultStorageLocation,
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "events.events.k8s.io", api.RestorePhaseNew).Result(),
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "events.events.k8s.io", velerov1api.RestorePhaseNew).Result(),
backup: defaultBackup().StorageLocation("default").Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseFailedValidation),
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
expectedValidationErrors: []string{
"events.events.k8s.io are non-restorable resources",
"Invalid included/excluded resource lists: excludes list cannot contain an item in the includes list: events.events.k8s.io",
@ -354,10 +358,10 @@ func TestProcessQueueItem(t *testing.T) {
{
name: "restoration of backups.velero.io is not supported",
location: defaultStorageLocation,
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "backups.velero.io", api.RestorePhaseNew).Result(),
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "backups.velero.io", velerov1api.RestorePhaseNew).Result(),
backup: defaultBackup().StorageLocation("default").Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseFailedValidation),
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
expectedValidationErrors: []string{
"backups.velero.io are non-restorable resources",
"Invalid included/excluded resource lists: excludes list cannot contain an item in the includes list: backups.velero.io",
@ -366,10 +370,10 @@ func TestProcessQueueItem(t *testing.T) {
{
name: "restoration of restores.velero.io is not supported",
location: defaultStorageLocation,
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "restores.velero.io", api.RestorePhaseNew).Result(),
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "restores.velero.io", velerov1api.RestorePhaseNew).Result(),
backup: defaultBackup().StorageLocation("default").Result(),
expectedErr: false,
expectedPhase: string(api.RestorePhaseFailedValidation),
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
expectedValidationErrors: []string{
"restores.velero.io are non-restorable resources",
"Invalid included/excluded resource lists: excludes list cannot contain an item in the includes list: restores.velero.io",
@ -378,9 +382,9 @@ func TestProcessQueueItem(t *testing.T) {
{
name: "backup download error results in failed restore",
location: defaultStorageLocation,
restore: NewRestore(api.DefaultNamespace, "bar", "backup-1", "ns-1", "", api.RestorePhaseNew).Result(),
expectedPhase: string(api.RestorePhaseInProgress),
expectedFinalPhase: string(api.RestorePhaseFailed),
restore: NewRestore(velerov1api.DefaultNamespace, "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Result(),
expectedPhase: string(velerov1api.RestorePhaseInProgress),
expectedFinalPhase: string(velerov1api.RestorePhaseFailed),
backupStoreGetBackupContentsErr: errors.New("Couldn't download backup"),
backup: defaultBackup().StorageLocation("default").Result(),
},
@ -392,6 +396,7 @@ func TestProcessQueueItem(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var (
client = fake.NewSimpleClientset()
fakeClient = newFakeClient(t)
restorer = &fakeRestorer{}
sharedInformers = informers.NewSharedInformerFactory(client, 0)
logger = velerotest.NewLogger()
@ -403,13 +408,13 @@ func TestProcessQueueItem(t *testing.T) {
defer backupStore.AssertExpectations(t)
c := NewRestoreController(
api.DefaultNamespace,
velerov1api.DefaultNamespace,
sharedInformers.Velero().V1().Restores(),
client.VeleroV1(),
client.VeleroV1(),
restorer,
sharedInformers.Velero().V1().Backups().Lister(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
fakeClient,
sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
logger,
logrus.InfoLevel,
@ -419,12 +424,12 @@ func TestProcessQueueItem(t *testing.T) {
formatFlag,
).(*restoreController)
c.newBackupStore = func(*api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) {
c.newBackupStore = func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) {
return backupStore, nil
}
if test.location != nil {
sharedInformers.Velero().V1().BackupStorageLocations().Informer().GetStore().Add(test.location)
require.NoError(t, fakeClient.Create(context.Background(), test.location))
}
if test.backup != nil {
sharedInformers.Velero().V1().Backups().Informer().GetStore().Add(test.backup)
@ -462,7 +467,7 @@ func TestProcessQueueItem(t *testing.T) {
// these are the fields that we expect to be set by
// the controller
res.Status.Phase = api.RestorePhase(phase)
res.Status.Phase = velerov1api.RestorePhase(phase)
backupName, found, err := unstructured.NestedString(patchMap, "spec", "backupName")
if found {
@ -546,9 +551,9 @@ func TestProcessQueueItem(t *testing.T) {
}
type StatusPatch struct {
Phase api.RestorePhase `json:"phase"`
ValidationErrors []string `json:"validationErrors"`
Errors int `json:"errors"`
Phase velerov1api.RestorePhase `json:"phase"`
ValidationErrors []string `json:"validationErrors"`
Errors int `json:"errors"`
}
type Patch struct {
@ -568,7 +573,7 @@ func TestProcessQueueItem(t *testing.T) {
expected := Patch{
Status: StatusPatch{
Phase: api.RestorePhase(test.expectedPhase),
Phase: velerov1api.RestorePhase(test.expectedPhase),
ValidationErrors: test.expectedValidationErrors,
},
}
@ -593,7 +598,7 @@ func TestProcessQueueItem(t *testing.T) {
expected = Patch{
Status: StatusPatch{
Phase: api.RestorePhaseCompleted,
Phase: velerov1api.RestorePhaseCompleted,
Errors: test.expectedRestoreErrors,
},
}
@ -601,7 +606,7 @@ func TestProcessQueueItem(t *testing.T) {
if test.expectedFinalPhase != "" {
expected = Patch{
Status: StatusPatch{
Phase: api.RestorePhase(test.expectedFinalPhase),
Phase: velerov1api.RestorePhase(test.expectedFinalPhase),
Errors: test.expectedRestoreErrors,
},
}
@ -628,13 +633,13 @@ func TestvalidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
)
c := NewRestoreController(
api.DefaultNamespace,
velerov1api.DefaultNamespace,
sharedInformers.Velero().V1().Restores(),
client.VeleroV1(),
client.VeleroV1(),
nil,
sharedInformers.Velero().V1().Backups().Lister(),
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
nil,
sharedInformers.Velero().V1().VolumeSnapshotLocations().Lister(),
logger,
logrus.DebugLevel,
@ -644,12 +649,12 @@ func TestvalidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
formatFlag,
).(*restoreController)
restore := &api.Restore{
restore := &velerov1api.Restore{
ObjectMeta: metav1.ObjectMeta{
Namespace: api.DefaultNamespace,
Namespace: velerov1api.DefaultNamespace,
Name: "restore-1",
},
Spec: api.RestoreSpec{
Spec: velerov1api.RestoreSpec{
ScheduleName: "schedule-1",
},
}
@ -657,8 +662,8 @@ func TestvalidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
// no backups created from the schedule: fail validation
require.NoError(t, sharedInformers.Velero().V1().Backups().Informer().GetStore().Add(
defaultBackup().
ObjectMeta(builder.WithLabels(api.ScheduleNameLabel, "non-matching-schedule")).
Phase(api.BackupPhaseCompleted).
ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "non-matching-schedule")).
Phase(velerov1api.BackupPhaseCompleted).
Result(),
))
@ -671,9 +676,9 @@ func TestvalidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
defaultBackup().
ObjectMeta(
builder.WithName("backup-2"),
builder.WithLabels(api.ScheduleNameLabel, "schedule-1"),
builder.WithLabels(velerov1api.ScheduleNameLabel, "schedule-1"),
).
Phase(api.BackupPhaseInProgress).
Phase(velerov1api.BackupPhaseInProgress).
Result(),
))
@ -688,9 +693,9 @@ func TestvalidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
defaultBackup().
ObjectMeta(
builder.WithName("foo"),
builder.WithLabels(api.ScheduleNameLabel, "schedule-1"),
builder.WithLabels(velerov1api.ScheduleNameLabel, "schedule-1"),
).
Phase(api.BackupPhaseCompleted).
Phase(velerov1api.BackupPhaseCompleted).
StartTimestamp(now).
Result(),
))
@ -698,9 +703,9 @@ func TestvalidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
defaultBackup().
ObjectMeta(
builder.WithName("foo"),
builder.WithLabels(api.ScheduleNameLabel, "schedule-1"),
builder.WithLabels(velerov1api.ScheduleNameLabel, "schedule-1"),
).
Phase(api.BackupPhaseCompleted).
Phase(velerov1api.BackupPhaseCompleted).
StartTimestamp(now.Add(time.Second)).
Result(),
))
@ -711,7 +716,7 @@ func TestvalidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
}
func TestBackupXorScheduleProvided(t *testing.T) {
r := &api.Restore{}
r := &velerov1api.Restore{}
assert.False(t, backupXorScheduleProvided(r))
r.Spec.BackupName = "backup-1"
@ -728,12 +733,12 @@ func TestBackupXorScheduleProvided(t *testing.T) {
}
func TestMostRecentCompletedBackup(t *testing.T) {
backups := []*api.Backup{
backups := []*velerov1api.Backup{
{
ObjectMeta: metav1.ObjectMeta{
Name: "a",
},
Status: api.BackupStatus{
Status: velerov1api.BackupStatus{
Phase: "",
},
},
@ -741,32 +746,32 @@ func TestMostRecentCompletedBackup(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "b",
},
Status: api.BackupStatus{
Phase: api.BackupPhaseNew,
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseNew,
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "c",
},
Status: api.BackupStatus{
Phase: api.BackupPhaseInProgress,
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseInProgress,
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "d",
},
Status: api.BackupStatus{
Phase: api.BackupPhaseFailedValidation,
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFailedValidation,
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "e",
},
Status: api.BackupStatus{
Phase: api.BackupPhaseFailed,
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFailed,
},
},
}
@ -775,22 +780,22 @@ func TestMostRecentCompletedBackup(t *testing.T) {
now := time.Now()
backups = append(backups, &api.Backup{
backups = append(backups, &velerov1api.Backup{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Status: api.BackupStatus{
Phase: api.BackupPhaseCompleted,
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseCompleted,
StartTimestamp: &metav1.Time{Time: now},
},
})
expected := &api.Backup{
expected := &velerov1api.Backup{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
},
Status: api.BackupStatus{
Phase: api.BackupPhaseCompleted,
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseCompleted,
StartTimestamp: &metav1.Time{Time: now.Add(time.Second)},
},
}
@ -799,7 +804,7 @@ func TestMostRecentCompletedBackup(t *testing.T) {
assert.Equal(t, expected, mostRecentCompletedBackup(backups))
}
func NewRestore(ns, name, backup, includeNS, includeResource string, phase api.RestorePhase) *builder.RestoreBuilder {
func NewRestore(ns, name, backup, includeNS, includeResource string, phase velerov1api.RestorePhase) *builder.RestoreBuilder {
restore := builder.ForRestore(ns, name).Phase(phase).Backup(backup)
if includeNS != "" {
@ -817,7 +822,7 @@ func NewRestore(ns, name, backup, includeNS, includeResource string, phase api.R
type fakeRestorer struct {
mock.Mock
calledWithArg api.Restore
calledWithArg velerov1api.Restore
}
func (r *fakeRestorer) Restore(

View File

@ -0,0 +1,37 @@
/*
Copyright 2020 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 controller
import (
"testing"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func newFakeClient(t *testing.T, initObjs ...runtime.Object) client.Client {
err := velerov1api.AddToScheme(scheme.Scheme)
require.NoError(t, err)
return fake.NewFakeClientWithScheme(scheme.Scheme, initObjs...)
}

View File

@ -19,6 +19,8 @@ package install
import (
"time"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
corev1 "k8s.io/api/core/v1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -26,9 +28,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/config/crd/crds"
"github.com/vmware-tanzu/velero/pkg/buildinfo"
"github.com/vmware-tanzu/velero/pkg/generated/crds"
)
// Use "latest" if the build process didn't supply a version
@ -137,17 +138,17 @@ func Namespace(namespace string) *corev1.Namespace {
}
}
func BackupStorageLocation(namespace, provider, bucket, prefix string, config map[string]string, caCert []byte) *v1.BackupStorageLocation {
return &v1.BackupStorageLocation{
func BackupStorageLocation(namespace, provider, bucket, prefix string, config map[string]string, caCert []byte) *velerov1api.BackupStorageLocation {
return &velerov1api.BackupStorageLocation{
ObjectMeta: objectMeta(namespace, "default"),
TypeMeta: metav1.TypeMeta{
Kind: "BackupStorageLocation",
APIVersion: v1.SchemeGroupVersion.String(),
APIVersion: velerov1api.SchemeGroupVersion.String(),
},
Spec: v1.BackupStorageLocationSpec{
Spec: velerov1api.BackupStorageLocationSpec{
Provider: provider,
StorageType: v1.StorageType{
ObjectStorage: &v1.ObjectStorageLocation{
StorageType: velerov1api.StorageType{
ObjectStorage: &velerov1api.ObjectStorageLocation{
Bucket: bucket,
Prefix: prefix,
CACert: caCert,
@ -158,14 +159,14 @@ func BackupStorageLocation(namespace, provider, bucket, prefix string, config ma
}
}
func VolumeSnapshotLocation(namespace, provider string, config map[string]string) *v1.VolumeSnapshotLocation {
return &v1.VolumeSnapshotLocation{
func VolumeSnapshotLocation(namespace, provider string, config map[string]string) *velerov1api.VolumeSnapshotLocation {
return &velerov1api.VolumeSnapshotLocation{
ObjectMeta: objectMeta(namespace, "default"),
TypeMeta: metav1.TypeMeta{
Kind: "VolumeSnapshotLocation",
APIVersion: v1.SchemeGroupVersion.String(),
APIVersion: velerov1api.SchemeGroupVersion.String(),
},
Spec: v1.VolumeSnapshotLocationSpec{
Spec: velerov1api.VolumeSnapshotLocationSpec{
Provider: provider,
Config: config,
},

View File

@ -17,11 +17,14 @@ limitations under the License.
package restic
import (
"context"
"fmt"
"os"
"strings"
"time"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/pkg/errors"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -284,17 +287,20 @@ func TempCACertFile(caCert []byte, bsl string, fs filesystem.Interface) (string,
return name, nil
}
func GetCACert(backupLocationLister velerov1listers.BackupStorageLocationLister, namespace, bsl string) ([]byte, error) {
location, err := backupLocationLister.BackupStorageLocations(namespace).Get(bsl)
if err != nil {
return nil, errors.Wrap(err, "error getting backup storage location")
func GetCACert(client kbclient.Client, namespace, backupLocation string) ([]byte, error) {
location := &velerov1api.BackupStorageLocation{}
if err := client.Get(context.Background(), kbclient.ObjectKey{
Namespace: namespace,
Name: backupLocation,
}, location); err != nil {
return nil, err
}
if location.Spec.ObjectStorage != nil {
return location.Spec.ObjectStorage.CACert, nil
if location.Spec.ObjectStorage == nil {
return nil, nil
}
return nil, nil
return location.Spec.ObjectStorage.CACert, nil
}
// NewPodVolumeRestoreListOptions creates a ListOptions with a label selector configured to
@ -309,10 +315,13 @@ func NewPodVolumeRestoreListOptions(name string) metav1.ListOptions {
// should be used when running a restic command for an Azure backend. This list is
// the current environment, plus the Azure-specific variables restic needs, namely
// a storage account name and key.
func AzureCmdEnv(backupLocationLister velerov1listers.BackupStorageLocationLister, namespace, backupLocation string) ([]string, error) {
loc, err := backupLocationLister.BackupStorageLocations(namespace).Get(backupLocation)
if err != nil {
return nil, errors.Wrap(err, "error getting backup storage location")
func AzureCmdEnv(client kbclient.Client, namespace, backupLocation string) ([]string, error) {
loc := &velerov1api.BackupStorageLocation{}
if err := client.Get(context.Background(), kbclient.ObjectKey{
Namespace: namespace,
Name: backupLocation,
}, loc); err != nil {
return nil, err
}
azureVars, err := getAzureResticEnvVars(loc.Spec.Config)
@ -332,10 +341,13 @@ func AzureCmdEnv(backupLocationLister velerov1listers.BackupStorageLocationListe
// should be used when running a restic command for an S3 backend. This list is
// the current environment, plus the AWS-specific variables restic needs, namely
// a credential profile.
func S3CmdEnv(backupLocationLister velerov1listers.BackupStorageLocationLister, namespace, backupLocation string) ([]string, error) {
loc, err := backupLocationLister.BackupStorageLocations(namespace).Get(backupLocation)
if err != nil {
return nil, errors.Wrap(err, "error getting backup storage location")
func S3CmdEnv(client kbclient.Client, namespace, backupLocation string) ([]string, error) {
loc := &velerov1api.BackupStorageLocation{}
if err := client.Get(context.Background(), kbclient.ObjectKey{
Namespace: namespace,
Name: backupLocation,
}, loc); err != nil {
return nil, err
}
awsVars, err := getS3ResticEnvVars(loc.Spec.Config)

View File

@ -17,18 +17,22 @@ limitations under the License.
package restic
import (
"context"
"os"
"sort"
"testing"
velerov1listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
k8sfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
@ -381,10 +385,8 @@ func TestTempCredentialsFile(t *testing.T) {
func TestTempCACertFile(t *testing.T) {
var (
bslInformer = cache.NewSharedIndexInformer(nil, new(velerov1api.BackupStorageLocation), 0, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
bslLister = velerov1listers.NewBackupStorageLocationLister(bslInformer.GetIndexer())
fs = velerotest.NewFakeFileSystem()
bsl = &velerov1api.BackupStorageLocation{
fs = velerotest.NewFakeFileSystem()
bsl = &velerov1api.BackupStorageLocation{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Namespace: "velero",
@ -398,15 +400,11 @@ func TestTempCACertFile(t *testing.T) {
}
)
// bsl not in lister: expect an error
caCert, err := GetCACert(bslLister, "velero", "default")
assert.Error(t, err)
fakeClient := newFakeClient(t)
fakeClient.Create(context.Background(), bsl)
// now add bsl to lister
require.NoError(t, bslInformer.GetStore().Add(bsl))
// bsl in lister: expect temp file to be created with cacert value
caCert, err = GetCACert(bslLister, "velero", "default")
// expect temp file to be created with cacert value
caCert, err := GetCACert(fakeClient, bsl.Namespace, bsl.Name)
require.NoError(t, err)
fileName, err := TempCACertFile(caCert, "default", fs)
@ -521,3 +519,9 @@ func TestGetPodVolumesUsingRestic(t *testing.T) {
})
}
}
func newFakeClient(t *testing.T, initObjs ...runtime.Object) client.Client {
err := velerov1api.AddToScheme(scheme.Scheme)
require.NoError(t, err)
return k8sfake.NewFakeClientWithScheme(scheme.Scheme, initObjs...)
}

View File

@ -29,6 +29,8 @@ import (
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
@ -79,20 +81,19 @@ type RestorerFactory interface {
}
type repositoryManager struct {
namespace string
veleroClient clientset.Interface
secretsLister corev1listers.SecretLister
repoLister velerov1listers.ResticRepositoryLister
repoInformerSynced cache.InformerSynced
backupLocationLister velerov1listers.BackupStorageLocationLister
backupLocationInformerSynced cache.InformerSynced
log logrus.FieldLogger
repoLocker *repoLocker
repoEnsurer *repositoryEnsurer
fileSystem filesystem.Interface
ctx context.Context
pvcClient corev1client.PersistentVolumeClaimsGetter
pvClient corev1client.PersistentVolumesGetter
namespace string
veleroClient clientset.Interface
secretsLister corev1listers.SecretLister
repoLister velerov1listers.ResticRepositoryLister
repoInformerSynced cache.InformerSynced
kbClient kbclient.Client
log logrus.FieldLogger
repoLocker *repoLocker
repoEnsurer *repositoryEnsurer
fileSystem filesystem.Interface
ctx context.Context
pvcClient corev1client.PersistentVolumeClaimsGetter
pvClient corev1client.PersistentVolumesGetter
}
// NewRepositoryManager constructs a RepositoryManager.
@ -103,23 +104,22 @@ func NewRepositoryManager(
secretsInformer cache.SharedIndexInformer,
repoInformer velerov1informers.ResticRepositoryInformer,
repoClient velerov1client.ResticRepositoriesGetter,
backupLocationInformer velerov1informers.BackupStorageLocationInformer,
kbClient kbclient.Client,
pvcClient corev1client.PersistentVolumeClaimsGetter,
pvClient corev1client.PersistentVolumesGetter,
log logrus.FieldLogger,
) (RepositoryManager, error) {
rm := &repositoryManager{
namespace: namespace,
veleroClient: veleroClient,
secretsLister: corev1listers.NewSecretLister(secretsInformer.GetIndexer()),
repoLister: repoInformer.Lister(),
repoInformerSynced: repoInformer.Informer().HasSynced,
backupLocationLister: backupLocationInformer.Lister(),
backupLocationInformerSynced: backupLocationInformer.Informer().HasSynced,
pvcClient: pvcClient,
pvClient: pvClient,
log: log,
ctx: ctx,
namespace: namespace,
veleroClient: veleroClient,
secretsLister: corev1listers.NewSecretLister(secretsInformer.GetIndexer()),
repoLister: repoInformer.Lister(),
repoInformerSynced: repoInformer.Informer().HasSynced,
kbClient: kbClient,
pvcClient: pvcClient,
pvClient: pvClient,
log: log,
ctx: ctx,
repoLocker: newRepoLocker(),
repoEnsurer: newRepositoryEnsurer(repoInformer, repoClient, log),
@ -245,10 +245,11 @@ func (rm *repositoryManager) exec(cmd *Command, backupLocation string) error {
cmd.PasswordFile = file
// if there's a caCert on the ObjectStorage, write it to disk so that it can be passed to restic
caCert, err := GetCACert(rm.backupLocationLister, rm.namespace, backupLocation)
caCert, err := GetCACert(rm.kbClient, rm.namespace, backupLocation)
if err != nil {
return err
}
var caCertFile string
if caCert != nil {
caCertFile, err = TempCACertFile(caCert, backupLocation, rm.fileSystem)
@ -261,21 +262,13 @@ func (rm *repositoryManager) exec(cmd *Command, backupLocation string) error {
cmd.CACertFile = caCertFile
if strings.HasPrefix(cmd.RepoIdentifier, "azure") {
if !cache.WaitForCacheSync(rm.ctx.Done(), rm.backupLocationInformerSynced) {
return errors.New("timed out waiting for cache to sync")
}
env, err := AzureCmdEnv(rm.backupLocationLister, rm.namespace, backupLocation)
env, err := AzureCmdEnv(rm.kbClient, rm.namespace, backupLocation)
if err != nil {
return err
}
cmd.Env = env
} else if strings.HasPrefix(cmd.RepoIdentifier, "s3") {
if !cache.WaitForCacheSync(rm.ctx.Done(), rm.backupLocationInformerSynced) {
return errors.New("timed out waiting for cache to sync")
}
env, err := S3CmdEnv(rm.backupLocationLister, rm.namespace, backupLocation)
env, err := S3CmdEnv(rm.kbClient, rm.namespace, backupLocation)
if err != nil {
return err
}

View File

@ -53,8 +53,8 @@ Example:
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
velerov1api ""github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
)

View File

@ -43,8 +43,7 @@ Example:
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
velerov1api ""github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
)

View File

@ -43,8 +43,8 @@ Example:
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
velerov1api ""github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
)

View File

@ -51,8 +51,8 @@ Example:
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
velerov1api ""github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
)