mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-06 21:36:30 +00:00
Merge pull request #4785 from RafaeLeal/restore-status
Add ability to restore status on selected resources
This commit is contained in:
1
changelogs/unreleased/4785-RafaeLeal
Normal file
1
changelogs/unreleased/4785-RafaeLeal
Normal file
@@ -0,0 +1 @@
|
||||
Add ability to restore status on selected resources
|
||||
@@ -1773,6 +1773,26 @@ spec:
|
||||
PVs from snapshot (via the cloudprovider).
|
||||
nullable: true
|
||||
type: boolean
|
||||
restoreStatus:
|
||||
description: RestoreStatus specifies which resources we should restore
|
||||
the status field. If nil, no objects are included. Optional.
|
||||
nullable: true
|
||||
properties:
|
||||
excludedResources:
|
||||
description: ExcludedResources specifies the resources to which
|
||||
will not restore the status.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedResources:
|
||||
description: IncludedResources specifies the resources to which
|
||||
will restore the status. If empty, it applies to all resources.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
scheduleName:
|
||||
description: ScheduleName is the unique name of the Velero schedule
|
||||
to restore from. If specified, and BackupName is empty, Velero will
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -86,6 +86,12 @@ type RestoreSpec struct {
|
||||
// +nullable
|
||||
RestorePVs *bool `json:"restorePVs,omitempty"`
|
||||
|
||||
// RestoreStatus specifies which resources we should restore the status
|
||||
// field. If nil, no objects are included. Optional.
|
||||
// +optional
|
||||
// +nullable
|
||||
RestoreStatus *RestoreStatusSpec `json:"restoreStatus,omitempty"`
|
||||
|
||||
// PreserveNodePorts specifies whether to restore old nodePorts from backup.
|
||||
// +optional
|
||||
// +nullable
|
||||
@@ -113,6 +119,19 @@ type RestoreHooks struct {
|
||||
Resources []RestoreResourceHookSpec `json:"resources,omitempty"`
|
||||
}
|
||||
|
||||
type RestoreStatusSpec struct {
|
||||
// IncludedResources specifies the resources to which will restore the status.
|
||||
// If empty, it applies to all resources.
|
||||
// +optional
|
||||
// +nullable
|
||||
IncludedResources []string `json:"includedResources,omitempty"`
|
||||
|
||||
// ExcludedResources specifies the resources to which will not restore the status.
|
||||
// +optional
|
||||
// +nullable
|
||||
ExcludedResources []string `json:"excludedResources,omitempty"`
|
||||
}
|
||||
|
||||
// RestoreResourceHookSpec defines one or more RestoreResrouceHooks that should be executed based on
|
||||
// the rules defined for namespaces, resources, and label selector.
|
||||
type RestoreResourceHookSpec struct {
|
||||
|
||||
@@ -1278,6 +1278,11 @@ func (in *RestoreSpec) DeepCopyInto(out *RestoreSpec) {
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.RestoreStatus != nil {
|
||||
in, out := &in.RestoreStatus, &out.RestoreStatus
|
||||
*out = new(RestoreStatusSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.PreserveNodePorts != nil {
|
||||
in, out := &in.PreserveNodePorts, &out.PreserveNodePorts
|
||||
*out = new(bool)
|
||||
@@ -1334,6 +1339,31 @@ func (in *RestoreStatus) DeepCopy() *RestoreStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RestoreStatusSpec) DeepCopyInto(out *RestoreStatusSpec) {
|
||||
*out = *in
|
||||
if in.IncludedResources != nil {
|
||||
in, out := &in.IncludedResources, &out.IncludedResources
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ExcludedResources != nil {
|
||||
in, out := &in.ExcludedResources, &out.ExcludedResources
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreStatusSpec.
|
||||
func (in *RestoreStatusSpec) DeepCopy() *RestoreStatusSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RestoreStatusSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Schedule) DeepCopyInto(out *Schedule) {
|
||||
*out = *in
|
||||
|
||||
@@ -89,6 +89,11 @@ type Deletor interface {
|
||||
Delete(name string, opts metav1.DeleteOptions) error
|
||||
}
|
||||
|
||||
// StatusUpdater updates status field of a object
|
||||
type StatusUpdater interface {
|
||||
UpdateStatus(obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error)
|
||||
}
|
||||
|
||||
// Dynamic contains client methods that Velero needs for backing up and restoring resources.
|
||||
type Dynamic interface {
|
||||
Creator
|
||||
@@ -97,6 +102,7 @@ type Dynamic interface {
|
||||
Getter
|
||||
Patcher
|
||||
Deletor
|
||||
StatusUpdater
|
||||
}
|
||||
|
||||
// dynamicResourceClient implements Dynamic.
|
||||
@@ -129,3 +135,7 @@ func (d *dynamicResourceClient) Patch(name string, data []byte) (*unstructured.U
|
||||
func (d *dynamicResourceClient) Delete(name string, opts metav1.DeleteOptions) error {
|
||||
return d.resourceClient.Delete(context.TODO(), name, opts)
|
||||
}
|
||||
|
||||
func (d *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
|
||||
return d.resourceClient.UpdateStatus(context.TODO(), obj, opts)
|
||||
}
|
||||
|
||||
@@ -85,6 +85,8 @@ type CreateOptions struct {
|
||||
ExistingResourcePolicy string
|
||||
IncludeResources flag.StringArray
|
||||
ExcludeResources flag.StringArray
|
||||
StatusIncludeResources flag.StringArray
|
||||
StatusExcludeResources flag.StringArray
|
||||
NamespaceMappings flag.Map
|
||||
Selector flag.LabelSelector
|
||||
IncludeClusterResources flag.OptionalBool
|
||||
@@ -115,6 +117,8 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
|
||||
flags.Var(&o.IncludeResources, "include-resources", "Resources to include in the restore, formatted as resource.group, such as storageclasses.storage.k8s.io (use '*' for all resources).")
|
||||
flags.Var(&o.ExcludeResources, "exclude-resources", "Resources to exclude from the restore, formatted as resource.group, such as storageclasses.storage.k8s.io.")
|
||||
flags.StringVar(&o.ExistingResourcePolicy, "existing-resource-policy", "", "Restore Policy to be used during the restore workflow, can be - none or update")
|
||||
flags.Var(&o.StatusIncludeResources, "status-include-resources", "Resources to include in the restore status, formatted as resource.group, such as storageclasses.storage.k8s.io.")
|
||||
flags.Var(&o.StatusExcludeResources, "status-exclude-resources", "Resources to exclude from the restore status, formatted as resource.group, such as storageclasses.storage.k8s.io.")
|
||||
flags.VarP(&o.Selector, "selector", "l", "Only restore resources matching this label selector.")
|
||||
f := flags.VarPF(&o.RestoreVolumes, "restore-volumes", "", "Whether to restore volumes from snapshots.")
|
||||
// this allows the user to just specify "--restore-volumes" as shorthand for "--restore-volumes=true"
|
||||
@@ -279,6 +283,13 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
},
|
||||
}
|
||||
|
||||
if len([]string(o.StatusIncludeResources)) > 0 {
|
||||
restore.Spec.RestoreStatus = &api.RestoreStatusSpec{
|
||||
IncludedResources: o.StatusIncludeResources,
|
||||
ExcludedResources: o.StatusExcludeResources,
|
||||
}
|
||||
}
|
||||
|
||||
if printed, err := output.PrintWithFormat(c, restore); printed || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -206,6 +206,20 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
|
||||
req.Restore.Spec.ExcludedResources,
|
||||
)
|
||||
|
||||
// Get resource status includes-excludes. Defaults to excluding all resources
|
||||
restoreStatusIncludesExcludes := collections.GetResourceIncludesExcludes(
|
||||
kr.discoveryHelper,
|
||||
[]string{},
|
||||
[]string{"*"},
|
||||
)
|
||||
if req.Restore.Spec.RestoreStatus != nil {
|
||||
restoreStatusIncludesExcludes = collections.GetResourceIncludesExcludes(
|
||||
kr.discoveryHelper,
|
||||
req.Restore.Spec.RestoreStatus.IncludedResources,
|
||||
req.Restore.Spec.RestoreStatus.ExcludedResources,
|
||||
)
|
||||
}
|
||||
|
||||
// Get namespace includes-excludes.
|
||||
namespaceIncludesExcludes := collections.NewIncludesExcludes().
|
||||
Includes(req.Restore.Spec.IncludedNamespaces...).
|
||||
@@ -268,83 +282,85 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
|
||||
}
|
||||
|
||||
restoreCtx := &restoreContext{
|
||||
backup: req.Backup,
|
||||
backupReader: req.BackupReader,
|
||||
restore: req.Restore,
|
||||
resourceIncludesExcludes: resourceIncludesExcludes,
|
||||
namespaceIncludesExcludes: namespaceIncludesExcludes,
|
||||
chosenGrpVersToRestore: make(map[string]ChosenGroupVersion),
|
||||
selector: selector,
|
||||
OrSelectors: OrSelectors,
|
||||
log: req.Log,
|
||||
dynamicFactory: kr.dynamicFactory,
|
||||
fileSystem: kr.fileSystem,
|
||||
namespaceClient: kr.namespaceClient,
|
||||
restoreItemActions: resolvedActions,
|
||||
itemSnapshotterActions: resolvedItemSnapshotterActions,
|
||||
volumeSnapshotterGetter: volumeSnapshotterGetter,
|
||||
resticRestorer: resticRestorer,
|
||||
resticErrs: make(chan error),
|
||||
pvsToProvision: sets.NewString(),
|
||||
pvRestorer: pvRestorer,
|
||||
volumeSnapshots: req.VolumeSnapshots,
|
||||
podVolumeBackups: req.PodVolumeBackups,
|
||||
resourceTerminatingTimeout: kr.resourceTerminatingTimeout,
|
||||
resourceClients: make(map[resourceClientKey]client.Dynamic),
|
||||
restoredItems: make(map[velero.ResourceIdentifier]struct{}),
|
||||
renamedPVs: make(map[string]string),
|
||||
pvRenamer: kr.pvRenamer,
|
||||
discoveryHelper: kr.discoveryHelper,
|
||||
resourcePriorities: kr.resourcePriorities,
|
||||
resourceRestoreHooks: resourceRestoreHooks,
|
||||
hooksErrs: make(chan error),
|
||||
waitExecHookHandler: waitExecHookHandler,
|
||||
hooksContext: hooksCtx,
|
||||
hooksCancelFunc: hooksCancelFunc,
|
||||
restoreClient: kr.restoreClient,
|
||||
backup: req.Backup,
|
||||
backupReader: req.BackupReader,
|
||||
restore: req.Restore,
|
||||
resourceIncludesExcludes: resourceIncludesExcludes,
|
||||
resourceStatusIncludesExcludes: restoreStatusIncludesExcludes,
|
||||
namespaceIncludesExcludes: namespaceIncludesExcludes,
|
||||
chosenGrpVersToRestore: make(map[string]ChosenGroupVersion),
|
||||
selector: selector,
|
||||
OrSelectors: OrSelectors,
|
||||
log: req.Log,
|
||||
dynamicFactory: kr.dynamicFactory,
|
||||
fileSystem: kr.fileSystem,
|
||||
namespaceClient: kr.namespaceClient,
|
||||
restoreItemActions: resolvedActions,
|
||||
itemSnapshotterActions: resolvedItemSnapshotterActions,
|
||||
volumeSnapshotterGetter: volumeSnapshotterGetter,
|
||||
resticRestorer: resticRestorer,
|
||||
resticErrs: make(chan error),
|
||||
pvsToProvision: sets.NewString(),
|
||||
pvRestorer: pvRestorer,
|
||||
volumeSnapshots: req.VolumeSnapshots,
|
||||
podVolumeBackups: req.PodVolumeBackups,
|
||||
resourceTerminatingTimeout: kr.resourceTerminatingTimeout,
|
||||
resourceClients: make(map[resourceClientKey]client.Dynamic),
|
||||
restoredItems: make(map[velero.ResourceIdentifier]struct{}),
|
||||
renamedPVs: make(map[string]string),
|
||||
pvRenamer: kr.pvRenamer,
|
||||
discoveryHelper: kr.discoveryHelper,
|
||||
resourcePriorities: kr.resourcePriorities,
|
||||
resourceRestoreHooks: resourceRestoreHooks,
|
||||
hooksErrs: make(chan error),
|
||||
waitExecHookHandler: waitExecHookHandler,
|
||||
hooksContext: hooksCtx,
|
||||
hooksCancelFunc: hooksCancelFunc,
|
||||
restoreClient: kr.restoreClient,
|
||||
}
|
||||
|
||||
return restoreCtx.execute()
|
||||
}
|
||||
|
||||
type restoreContext struct {
|
||||
backup *velerov1api.Backup
|
||||
backupReader io.Reader
|
||||
restore *velerov1api.Restore
|
||||
restoreDir string
|
||||
restoreClient velerov1client.RestoresGetter
|
||||
resourceIncludesExcludes *collections.IncludesExcludes
|
||||
namespaceIncludesExcludes *collections.IncludesExcludes
|
||||
chosenGrpVersToRestore map[string]ChosenGroupVersion
|
||||
selector labels.Selector
|
||||
OrSelectors []labels.Selector
|
||||
log logrus.FieldLogger
|
||||
dynamicFactory client.DynamicFactory
|
||||
fileSystem filesystem.Interface
|
||||
namespaceClient corev1.NamespaceInterface
|
||||
restoreItemActions []framework.RestoreItemResolvedAction
|
||||
itemSnapshotterActions []framework.ItemSnapshotterResolvedAction
|
||||
volumeSnapshotterGetter VolumeSnapshotterGetter
|
||||
resticRestorer restic.Restorer
|
||||
resticWaitGroup sync.WaitGroup
|
||||
resticErrs chan error
|
||||
pvsToProvision sets.String
|
||||
pvRestorer PVRestorer
|
||||
volumeSnapshots []*volume.Snapshot
|
||||
podVolumeBackups []*velerov1api.PodVolumeBackup
|
||||
resourceTerminatingTimeout time.Duration
|
||||
resourceClients map[resourceClientKey]client.Dynamic
|
||||
restoredItems map[velero.ResourceIdentifier]struct{}
|
||||
renamedPVs map[string]string
|
||||
pvRenamer func(string) (string, error)
|
||||
discoveryHelper discovery.Helper
|
||||
resourcePriorities []string
|
||||
hooksWaitGroup sync.WaitGroup
|
||||
hooksErrs chan error
|
||||
resourceRestoreHooks []hook.ResourceRestoreHook
|
||||
waitExecHookHandler hook.WaitExecHookHandler
|
||||
hooksContext go_context.Context
|
||||
hooksCancelFunc go_context.CancelFunc
|
||||
backup *velerov1api.Backup
|
||||
backupReader io.Reader
|
||||
restore *velerov1api.Restore
|
||||
restoreDir string
|
||||
restoreClient velerov1client.RestoresGetter
|
||||
resourceIncludesExcludes *collections.IncludesExcludes
|
||||
resourceStatusIncludesExcludes *collections.IncludesExcludes
|
||||
namespaceIncludesExcludes *collections.IncludesExcludes
|
||||
chosenGrpVersToRestore map[string]ChosenGroupVersion
|
||||
selector labels.Selector
|
||||
OrSelectors []labels.Selector
|
||||
log logrus.FieldLogger
|
||||
dynamicFactory client.DynamicFactory
|
||||
fileSystem filesystem.Interface
|
||||
namespaceClient corev1.NamespaceInterface
|
||||
restoreItemActions []framework.RestoreItemResolvedAction
|
||||
itemSnapshotterActions []framework.ItemSnapshotterResolvedAction
|
||||
volumeSnapshotterGetter VolumeSnapshotterGetter
|
||||
resticRestorer restic.Restorer
|
||||
resticWaitGroup sync.WaitGroup
|
||||
resticErrs chan error
|
||||
pvsToProvision sets.String
|
||||
pvRestorer PVRestorer
|
||||
volumeSnapshots []*volume.Snapshot
|
||||
podVolumeBackups []*velerov1api.PodVolumeBackup
|
||||
resourceTerminatingTimeout time.Duration
|
||||
resourceClients map[resourceClientKey]client.Dynamic
|
||||
restoredItems map[velero.ResourceIdentifier]struct{}
|
||||
renamedPVs map[string]string
|
||||
pvRenamer func(string) (string, error)
|
||||
discoveryHelper discovery.Helper
|
||||
resourcePriorities []string
|
||||
hooksWaitGroup sync.WaitGroup
|
||||
hooksErrs chan error
|
||||
resourceRestoreHooks []hook.ResourceRestoreHook
|
||||
waitExecHookHandler hook.WaitExecHookHandler
|
||||
hooksContext go_context.Context
|
||||
hooksCancelFunc go_context.CancelFunc
|
||||
}
|
||||
|
||||
type resourceClientKey struct {
|
||||
@@ -1111,19 +1127,21 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||
}
|
||||
}
|
||||
|
||||
objStatus, statusFieldExists, statusFieldErr := unstructured.NestedFieldCopy(obj.Object, "status")
|
||||
// Clear out non-core metadata fields and status.
|
||||
if obj, err = resetMetadataAndStatus(obj); err != nil {
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
ctx.log.Infof("restore status includes excludes: %+v", ctx.resourceStatusIncludesExcludes)
|
||||
|
||||
for _, action := range ctx.getApplicableActions(groupResource, namespace) {
|
||||
if !action.Selector.Matches(labels.Set(obj.GetLabels())) {
|
||||
continue
|
||||
}
|
||||
|
||||
ctx.log.Infof("Executing item action for %v", &groupResource)
|
||||
|
||||
executeOutput, err := action.RestoreItemAction.Execute(&velero.RestoreItemActionExecuteInput{
|
||||
Item: obj,
|
||||
ItemFromBackup: itemFromBackup,
|
||||
@@ -1344,6 +1362,29 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
shouldRestoreStatus := ctx.resourceStatusIncludesExcludes.ShouldInclude(groupResource.String())
|
||||
if shouldRestoreStatus && statusFieldErr != nil {
|
||||
err := fmt.Errorf("could not get status to be restored %s: %v", kube.NamespaceAndName(obj), statusFieldErr)
|
||||
ctx.log.Errorf(err.Error())
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs
|
||||
}
|
||||
// if it should restore status, run a UpdateStatus
|
||||
if statusFieldExists && shouldRestoreStatus {
|
||||
if err := unstructured.SetNestedField(obj.Object, objStatus, "status"); err != nil {
|
||||
ctx.log.Errorf("could not set status field %s: %v", kube.NamespaceAndName(obj), err)
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs
|
||||
}
|
||||
obj.SetResourceVersion(createdObj.GetResourceVersion())
|
||||
updated, err := resourceClient.UpdateStatus(obj, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
warnings.Add(namespace, err)
|
||||
} else {
|
||||
createdObj = updated
|
||||
}
|
||||
}
|
||||
|
||||
if groupResource == kuberesource.Pods {
|
||||
pod := new(v1.Pod)
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pod); err != nil {
|
||||
@@ -1631,7 +1672,7 @@ func resetVolumeBindingInfo(obj *unstructured.Unstructured) *unstructured.Unstru
|
||||
return obj
|
||||
}
|
||||
|
||||
func resetMetadataAndStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
func resetMetadata(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
res, ok := obj.Object["metadata"]
|
||||
if !ok {
|
||||
return nil, errors.New("metadata not found")
|
||||
@@ -1649,9 +1690,19 @@ func resetMetadataAndStatus(obj *unstructured.Unstructured) (*unstructured.Unstr
|
||||
}
|
||||
}
|
||||
|
||||
// Never restore status
|
||||
delete(obj.UnstructuredContent(), "status")
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func resetStatus(obj *unstructured.Unstructured) {
|
||||
unstructured.RemoveNestedField(obj.UnstructuredContent(), "status")
|
||||
}
|
||||
|
||||
func resetMetadataAndStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
_, err := resetMetadata(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resetStatus(obj)
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1867,6 +1867,7 @@ func assertRestoredItems(t *testing.T, h *harness, want []*test.APIResource) {
|
||||
// empty in the structured objects. Remove them to make comparison easier.
|
||||
unstructured.RemoveNestedField(want.Object, "metadata", "creationTimestamp")
|
||||
unstructured.RemoveNestedField(want.Object, "status")
|
||||
unstructured.RemoveNestedField(res.Object, "status")
|
||||
|
||||
assert.Equal(t, want, res)
|
||||
}
|
||||
@@ -2805,7 +2806,7 @@ func TestRestoreWithRestic(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetMetadataAndStatus(t *testing.T) {
|
||||
func TestResetMetadata(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj *unstructured.Unstructured
|
||||
@@ -2824,20 +2825,46 @@ func TestResetMetadataAndStatus(t *testing.T) {
|
||||
expectedRes: NewTestUnstructured().WithMetadata("name", "namespace", "labels", "annotations").Unstructured,
|
||||
},
|
||||
{
|
||||
name: "don't keep status",
|
||||
name: "keep status",
|
||||
obj: NewTestUnstructured().WithMetadata().WithStatus().Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithMetadata().WithStatus().Unstructured,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := resetMetadata(test.obj)
|
||||
|
||||
if assert.Equal(t, test.expectedErr, err != nil) {
|
||||
assert.Equal(t, test.expectedRes, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj *unstructured.Unstructured
|
||||
expectedRes *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "no status don't cause error",
|
||||
obj: &unstructured.Unstructured{},
|
||||
expectedRes: &unstructured.Unstructured{},
|
||||
},
|
||||
{
|
||||
name: "remove status",
|
||||
obj: NewTestUnstructured().WithMetadata().WithStatus().Unstructured,
|
||||
expectedRes: NewTestUnstructured().WithMetadata().Unstructured,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := resetMetadataAndStatus(test.obj)
|
||||
|
||||
if assert.Equal(t, test.expectedErr, err != nil) {
|
||||
assert.Equal(t, test.expectedRes, res)
|
||||
}
|
||||
resetStatus(test.obj)
|
||||
assert.Equal(t, test.expectedRes, test.obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,3 +72,8 @@ func (c *FakeDynamicClient) Delete(name string, opts metav1.DeleteOptions) error
|
||||
args := c.Called(name, opts)
|
||||
return args.Error(1)
|
||||
}
|
||||
|
||||
func (c *FakeDynamicClient) UpdateStatus(obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
|
||||
args := c.Called(obj, opts)
|
||||
return args.Get(0).(*unstructured.Unstructured), args.Error(1)
|
||||
}
|
||||
|
||||
@@ -46,6 +46,21 @@ spec:
|
||||
# or fully-qualified. Optional.
|
||||
excludedResources:
|
||||
- storageclasses.storage.k8s.io
|
||||
|
||||
# restoreStatus selects resources to restore not only the specification, but
|
||||
# the status of the manifest. This is specially useful for CRDs that maintain
|
||||
# external references. By default, it excludes all resources.
|
||||
restoreStatus:
|
||||
# Array of resources to include in the restore status. Just like above,
|
||||
# resources may be shortcuts (for example 'po' for 'pods') or fully-qualified.
|
||||
# If unspecified, no resources are included. Optional.
|
||||
includedResources:
|
||||
- workflows
|
||||
# Array of resources to exclude from the restore status. Resources may be
|
||||
# shortcuts (for example 'po' for 'pods') or fully-qualified.
|
||||
# If unspecified, all resources are excluded. Optional.
|
||||
excludedResources: []
|
||||
|
||||
# Whether or not to include cluster-scoped resources. Valid values are true, false, and
|
||||
# null/unset. If true, all cluster-scoped resources are included (subject to included/excluded
|
||||
# resources and the label selector). If false, no cluster-scoped resources are included. If unset,
|
||||
|
||||
Reference in New Issue
Block a user