✨ Implement restore hooks injecting init containers into pod spec (#2787)
* ✨ Implement restore hooks injecting init containers into pod spec
Signed-off-by: Ashish Amarnath <ashisham@vmware.com>
pull/2802/head
parent
9b9bb62968
commit
5d6da6517b
|
@ -0,0 +1,2 @@
|
|||
Implement restore hooks injecting init containers into pod spec
|
||||
|
|
@ -19,20 +19,26 @@ package hook
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
uuid "github.com/gofrs/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
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/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/podexec"
|
||||
"github.com/vmware-tanzu/velero/pkg/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
)
|
||||
|
||||
type hookPhase string
|
||||
|
@ -76,6 +82,87 @@ type ItemHookHandler interface {
|
|||
) error
|
||||
}
|
||||
|
||||
// ItemRestoreHookHandler invokes restore hooks for an item
|
||||
type ItemRestoreHookHandler interface {
|
||||
HandleRestoreHooks(
|
||||
log logrus.FieldLogger,
|
||||
groupResource schema.GroupResource,
|
||||
obj runtime.Unstructured,
|
||||
rh []ResourceRestoreHook,
|
||||
) (runtime.Unstructured, error)
|
||||
}
|
||||
|
||||
// InitContainerRestoreHookHandler is the restore hook handler to add init containers to restored pods.
|
||||
type InitContainerRestoreHookHandler struct{}
|
||||
|
||||
// HandleRestoreHooks runs the restore hooks for an item.
|
||||
// If the item is a pod, then hooks are chosen to be run as follows:
|
||||
// If the pod has the appropriate annotations specifying the hook action, then hooks from the annotation are run
|
||||
// Otherwise, the supplied ResourceRestoreHooks are applied.
|
||||
func (i *InitContainerRestoreHookHandler) HandleRestoreHooks(
|
||||
log logrus.FieldLogger,
|
||||
groupResource schema.GroupResource,
|
||||
obj runtime.Unstructured,
|
||||
resourceRestoreHooks []ResourceRestoreHook,
|
||||
) (runtime.Unstructured, error) {
|
||||
// We only support hooks on pods right now
|
||||
if groupResource != kuberesource.Pods {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
metadata, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to get a metadata accessor")
|
||||
}
|
||||
pod := new(corev1api.Pod)
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pod); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
initContainers := []corev1api.Container{}
|
||||
// If this pod had pod volumes backed up using restic, then we want to the pod volumes restored prior to
|
||||
// running the restore hook init containers. This allows the restore hook init containers to prepare the
|
||||
// restored data to be consumed by the application container(s).
|
||||
// So if there is a "resitc-wait" init container already on the pod at index 0, we'll preserve that and run
|
||||
// it before running any other init container.
|
||||
if len(pod.Spec.InitContainers) > 0 && pod.Spec.InitContainers[0].Name == restic.InitContainer {
|
||||
initContainers = append(initContainers, pod.Spec.InitContainers[0])
|
||||
pod.Spec.InitContainers = pod.Spec.InitContainers[1:]
|
||||
}
|
||||
|
||||
hooksFromAnnotations := getInitRestoreHookFromAnnotation(kube.NamespaceAndName(pod), metadata.GetAnnotations(), log)
|
||||
if hooksFromAnnotations != nil {
|
||||
log.Infof("Handling InitRestoreHooks from pod annotaions")
|
||||
initContainers = append(initContainers, hooksFromAnnotations.InitContainers...)
|
||||
} else {
|
||||
log.Infof("Handling InitRestoreHooks from RestoreSpec")
|
||||
// pod did not have the annotations appropriate for restore hooks
|
||||
// running applicable ResourceRestoreHooks supplied.
|
||||
namespace := metadata.GetNamespace()
|
||||
labels := labels.Set(metadata.GetLabels())
|
||||
|
||||
for _, rh := range resourceRestoreHooks {
|
||||
if !rh.Selector.applicableTo(groupResource, namespace, labels) {
|
||||
continue
|
||||
}
|
||||
for _, hook := range rh.RestoreHooks {
|
||||
if hook.Init != nil {
|
||||
initContainers = append(initContainers, hook.Init.InitContainers...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pod.Spec.InitContainers = append(initContainers, pod.Spec.InitContainers...)
|
||||
log.Infof("Returning pod %s/%s with %d init container(s)", pod.Namespace, pod.Name, len(pod.Spec.InitContainers))
|
||||
|
||||
podMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&pod)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return &unstructured.Unstructured{Object: podMap}, nil
|
||||
}
|
||||
|
||||
// DefaultItemHookHandler is the default itemHookHandler.
|
||||
type DefaultItemHookHandler struct {
|
||||
PodCommandExecutor podexec.PodCommandExecutor
|
||||
|
@ -117,7 +204,7 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
|||
)
|
||||
if err := h.PodCommandExecutor.ExecutePodCommand(hookLog, obj.UnstructuredContent(), namespace, name, "<from-annotation>", hookFromAnnotations); err != nil {
|
||||
hookLog.WithError(err).Error("Error executing hook")
|
||||
if hookFromAnnotations.OnError == api.HookErrorModeFail {
|
||||
if hookFromAnnotations.OnError == velerov1api.HookErrorModeFail {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -128,11 +215,11 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
|||
labels := labels.Set(metadata.GetLabels())
|
||||
// Otherwise, check for hooks defined in the backup spec.
|
||||
for _, resourceHook := range resourceHooks {
|
||||
if !resourceHook.applicableTo(groupResource, namespace, labels) {
|
||||
if !resourceHook.Selector.applicableTo(groupResource, namespace, labels) {
|
||||
continue
|
||||
}
|
||||
|
||||
var hooks []api.BackupResourceHook
|
||||
var hooks []velerov1api.BackupResourceHook
|
||||
if phase == PhasePre {
|
||||
hooks = resourceHook.Pre
|
||||
} else {
|
||||
|
@ -151,7 +238,7 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
|||
err := h.PodCommandExecutor.ExecutePodCommand(hookLog, obj.UnstructuredContent(), namespace, name, resourceHook.Name, hook.Exec)
|
||||
if err != nil {
|
||||
hookLog.WithError(err).Error("Error executing hook")
|
||||
if hook.Exec.OnError == api.HookErrorModeFail {
|
||||
if hook.Exec.OnError == velerov1api.HookErrorModeFail {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -177,25 +264,16 @@ func getHookAnnotation(annotations map[string]string, key string, phase hookPhas
|
|||
// getPodExecHookFromAnnotations returns an ExecHook based on the annotations, as long as the
|
||||
// 'command' annotation is present. If it is absent, this returns nil.
|
||||
// If there is an error in parsing a supplied timeout, it is logged.
|
||||
func getPodExecHookFromAnnotations(annotations map[string]string, phase hookPhase, log logrus.FieldLogger) *api.ExecHook {
|
||||
func getPodExecHookFromAnnotations(annotations map[string]string, phase hookPhase, log logrus.FieldLogger) *velerov1api.ExecHook {
|
||||
commandValue := getHookAnnotation(annotations, podBackupHookCommandAnnotationKey, phase)
|
||||
if commandValue == "" {
|
||||
return nil
|
||||
}
|
||||
var command []string
|
||||
// check for json array
|
||||
if commandValue[0] == '[' {
|
||||
if err := json.Unmarshal([]byte(commandValue), &command); err != nil {
|
||||
command = []string{commandValue}
|
||||
}
|
||||
} else {
|
||||
command = append(command, commandValue)
|
||||
}
|
||||
|
||||
container := getHookAnnotation(annotations, podBackupHookContainerAnnotationKey, phase)
|
||||
|
||||
onError := api.HookErrorMode(getHookAnnotation(annotations, podBackupHookOnErrorAnnotationKey, phase))
|
||||
if onError != api.HookErrorModeContinue && onError != api.HookErrorModeFail {
|
||||
onError := velerov1api.HookErrorMode(getHookAnnotation(annotations, podBackupHookOnErrorAnnotationKey, phase))
|
||||
if onError != velerov1api.HookErrorModeContinue && onError != velerov1api.HookErrorModeFail {
|
||||
onError = ""
|
||||
}
|
||||
|
||||
|
@ -209,25 +287,42 @@ func getPodExecHookFromAnnotations(annotations map[string]string, phase hookPhas
|
|||
}
|
||||
}
|
||||
|
||||
return &api.ExecHook{
|
||||
return &velerov1api.ExecHook{
|
||||
Container: container,
|
||||
Command: command,
|
||||
Command: parseStringToCommand(commandValue),
|
||||
OnError: onError,
|
||||
Timeout: metav1.Duration{Duration: timeout},
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceHook is a hook for a given resource.
|
||||
type ResourceHook struct {
|
||||
Name string
|
||||
func parseStringToCommand(commandValue string) []string {
|
||||
var command []string
|
||||
// check for json array
|
||||
if commandValue[0] == '[' {
|
||||
if err := json.Unmarshal([]byte(commandValue), &command); err != nil {
|
||||
command = []string{commandValue}
|
||||
}
|
||||
} else {
|
||||
command = append(command, commandValue)
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
type ResourceHookSelector struct {
|
||||
Namespaces *collections.IncludesExcludes
|
||||
Resources *collections.IncludesExcludes
|
||||
LabelSelector labels.Selector
|
||||
Pre []api.BackupResourceHook
|
||||
Post []api.BackupResourceHook
|
||||
}
|
||||
|
||||
func (r ResourceHook) applicableTo(groupResource schema.GroupResource, namespace string, labels labels.Set) bool {
|
||||
// ResourceHook is a hook for a given resource.
|
||||
type ResourceHook struct {
|
||||
Name string
|
||||
Selector ResourceHookSelector
|
||||
Pre []velerov1api.BackupResourceHook
|
||||
Post []velerov1api.BackupResourceHook
|
||||
}
|
||||
|
||||
func (r ResourceHookSelector) applicableTo(groupResource schema.GroupResource, namespace string, labels labels.Set) bool {
|
||||
if r.Namespaces != nil && !r.Namespaces.ShouldInclude(namespace) {
|
||||
return false
|
||||
}
|
||||
|
@ -239,3 +334,76 @@ func (r ResourceHook) applicableTo(groupResource schema.GroupResource, namespace
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ResourceRestoreHook is a restore hook for a given resource.
|
||||
type ResourceRestoreHook struct {
|
||||
Name string
|
||||
Selector ResourceHookSelector
|
||||
RestoreHooks []velerov1api.RestoreResourceHook
|
||||
}
|
||||
|
||||
func getInitRestoreHookFromAnnotation(podName string, annotations map[string]string, log logrus.FieldLogger) *velerov1api.InitRestoreHook {
|
||||
containerImage := annotations[podRestoreHookInitContainerImageAnnotationKey]
|
||||
containerName := annotations[podRestoreHookInitContainerNameAnnotationKey]
|
||||
command := annotations[podRestoreHookInitContainerCommandAnnotationKey]
|
||||
if containerImage == "" {
|
||||
log.Infof("Pod %s has no %s annotation, no initRestoreHook in annotation", podName, podRestoreHookInitContainerImageAnnotationKey)
|
||||
return nil
|
||||
}
|
||||
if command == "" {
|
||||
log.Infof("RestoreHook init contianer for pod %s is using container's default entrypoint", podName, containerImage)
|
||||
}
|
||||
if containerName == "" {
|
||||
uid, err := uuid.NewV4()
|
||||
uuidStr := "deadfeed"
|
||||
if err != nil {
|
||||
log.Errorf("Failed to generate UUID for container name")
|
||||
} else {
|
||||
uuidStr = strings.Split(uid.String(), "-")[0]
|
||||
}
|
||||
containerName = fmt.Sprintf("velero-restore-init-%s", uuidStr)
|
||||
log.Infof("Pod %s has no %s annotation, using generated name %s for initContainer", podName, podRestoreHookInitContainerNameAnnotationKey, containerName)
|
||||
}
|
||||
|
||||
return &velerov1api.InitRestoreHook{
|
||||
InitContainers: []corev1api.Container{
|
||||
{
|
||||
Image: containerImage,
|
||||
Name: containerName,
|
||||
Command: parseStringToCommand(command),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetRestoreHooksFromSpec returns a list of ResourceRestoreHooks from the restore Spec.
|
||||
func GetRestoreHooksFromSpec(hooksSpec *velerov1api.RestoreHooks) ([]ResourceRestoreHook, error) {
|
||||
if hooksSpec == nil {
|
||||
return []ResourceRestoreHook{}, nil
|
||||
}
|
||||
restoreHooks := make([]ResourceRestoreHook, 0, len(hooksSpec.Resources))
|
||||
for _, rs := range hooksSpec.Resources {
|
||||
rh := ResourceRestoreHook{
|
||||
Name: rs.Name,
|
||||
Selector: ResourceHookSelector{
|
||||
Namespaces: collections.NewIncludesExcludes().Includes(rs.IncludedNamespaces...).Excludes(rs.ExcludedNamespaces...),
|
||||
// these hooks ara applicable only to pods.
|
||||
// TODO: resolve the pods resource via discovery?
|
||||
Resources: collections.NewIncludesExcludes().Includes(kuberesource.Pods.Resource),
|
||||
},
|
||||
// TODO does this work for ExecRestoreHook as well?
|
||||
RestoreHooks: rs.PostHooks,
|
||||
}
|
||||
|
||||
if rs.LabelSelector != nil {
|
||||
ls, err := metav1.LabelSelectorAsSelector(rs.LabelSelector)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
rh.Selector.LabelSelector = ls
|
||||
}
|
||||
restoreHooks = append(restoreHooks, rh)
|
||||
}
|
||||
|
||||
return restoreHooks, nil
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -197,11 +197,13 @@ func getResourceHooks(hookSpecs []velerov1api.BackupResourceHookSpec, discoveryH
|
|||
|
||||
func getResourceHook(hookSpec velerov1api.BackupResourceHookSpec, discoveryHelper discovery.Helper) (hook.ResourceHook, error) {
|
||||
h := hook.ResourceHook{
|
||||
Name: hookSpec.Name,
|
||||
Namespaces: collections.NewIncludesExcludes().Includes(hookSpec.IncludedNamespaces...).Excludes(hookSpec.ExcludedNamespaces...),
|
||||
Resources: getResourceIncludesExcludes(discoveryHelper, hookSpec.IncludedResources, hookSpec.ExcludedResources),
|
||||
Pre: hookSpec.PreHooks,
|
||||
Post: hookSpec.PostHooks,
|
||||
Name: hookSpec.Name,
|
||||
Selector: hook.ResourceHookSelector{
|
||||
Namespaces: collections.NewIncludesExcludes().Includes(hookSpec.IncludedNamespaces...).Excludes(hookSpec.ExcludedNamespaces...),
|
||||
Resources: getResourceIncludesExcludes(discoveryHelper, hookSpec.IncludedResources, hookSpec.ExcludedResources),
|
||||
},
|
||||
Pre: hookSpec.PreHooks,
|
||||
Post: hookSpec.PostHooks,
|
||||
}
|
||||
|
||||
if hookSpec.LabelSelector != nil {
|
||||
|
@ -209,7 +211,7 @@ func getResourceHook(hookSpec velerov1api.BackupResourceHookSpec, discoveryHelpe
|
|||
if err != nil {
|
||||
return hook.ResourceHook{}, errors.WithStack(err)
|
||||
}
|
||||
h.LabelSelector = labelSelector
|
||||
h.Selector.LabelSelector = labelSelector
|
||||
}
|
||||
|
||||
return h, nil
|
||||
|
|
|
@ -105,3 +105,13 @@ func (b *ContainerBuilder) PullPolicy(pullPolicy corev1api.PullPolicy) *Containe
|
|||
b.object.ImagePullPolicy = pullPolicy
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *ContainerBuilder) Command(command []string) *ContainerBuilder {
|
||||
if b.object.Command == nil {
|
||||
b.object.Command = []string{}
|
||||
}
|
||||
for _, c := range command {
|
||||
b.object.Command = append(b.object.Command, c)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
|
@ -56,6 +56,12 @@ func (b *PodBuilder) ObjectMeta(opts ...ObjectMetaOpt) *PodBuilder {
|
|||
return b
|
||||
}
|
||||
|
||||
// ServiceAccount sets serviceAccounts on pod.
|
||||
func (b *PodBuilder) ServiceAccount(sa string) *PodBuilder {
|
||||
b.object.Spec.ServiceAccountName = sa
|
||||
return b
|
||||
}
|
||||
|
||||
// Volumes appends to the pod's volumes
|
||||
func (b *PodBuilder) Volumes(volumes ...*corev1api.Volume) *PodBuilder {
|
||||
for _, v := range volumes {
|
||||
|
|
|
@ -44,6 +44,7 @@ func NewCommand(f client.Factory) *cobra.Command {
|
|||
RegisterRestoreItemAction("velero.io/job", newJobRestoreItemAction).
|
||||
RegisterRestoreItemAction("velero.io/pod", newPodRestoreItemAction).
|
||||
RegisterRestoreItemAction("velero.io/restic", newResticRestoreItemAction(f)).
|
||||
RegisterRestoreItemAction("velero.io/init-restore-hook", newInitRestoreHookPodAction).
|
||||
RegisterRestoreItemAction("velero.io/service", newServiceRestoreItemAction).
|
||||
RegisterRestoreItemAction("velero.io/service-account", newServiceAccountRestoreItemAction).
|
||||
RegisterRestoreItemAction("velero.io/add-pvc-from-pod", newAddPVCFromPodRestoreItemAction).
|
||||
|
@ -119,6 +120,10 @@ func newPodRestoreItemAction(logger logrus.FieldLogger) (interface{}, error) {
|
|||
return restore.NewPodAction(logger), nil
|
||||
}
|
||||
|
||||
func newInitRestoreHookPodAction(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return restore.NewInitRestoreHookPodAction(logger), nil
|
||||
}
|
||||
|
||||
func newResticRestoreItemAction(f client.Factory) veleroplugin.HandlerInitializer {
|
||||
return func(logger logrus.FieldLogger) (interface{}, error) {
|
||||
client, err := f.KubeClient()
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
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 restore
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/hook"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
)
|
||||
|
||||
// InitRestoreHookPodAction is a RestoreItemAction plugin applicable to pods that runs
|
||||
// restore hooks to add init containers to pods prior to them being restored.
|
||||
type InitRestoreHookPodAction struct {
|
||||
logger logrus.FieldLogger
|
||||
}
|
||||
|
||||
// NewInitRestoreHookPodAction returns a new InitRestoreHookPodAction.
|
||||
func NewInitRestoreHookPodAction(logger logrus.FieldLogger) *InitRestoreHookPodAction {
|
||||
return &InitRestoreHookPodAction{logger: logger}
|
||||
}
|
||||
|
||||
// AppliesTo implements the RestoreItemAction plugin intrface method.
|
||||
func (a *InitRestoreHookPodAction) AppliesTo() (velero.ResourceSelector, error) {
|
||||
return velero.ResourceSelector{
|
||||
IncludedResources: []string{"pods"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Execute implements the RestoreItemAction plugin interface method.
|
||||
func (a *InitRestoreHookPodAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
|
||||
a.logger.Infof("Executing InitRestoreHookPodAction")
|
||||
// handle any init container restore hooks for the pod
|
||||
restoreHooks, err := hook.GetRestoreHooksFromSpec(&input.Restore.Spec.Hooks)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
hookHandler := hook.InitContainerRestoreHookHandler{}
|
||||
postHooksItem, err := hookHandler.HandleRestoreHooks(a.logger, kuberesource.Pods, input.Item, restoreHooks)
|
||||
a.logger.Infof("Returning from InitRestoreHookPodAction")
|
||||
|
||||
return velero.NewRestoreItemActionExecuteOutput(&unstructured.Unstructured{Object: postHooksItem.UnstructuredContent()}), nil
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
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 restore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
func TestInitContainerRestoreHookPodActionExecute(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
obj *corev1api.Pod
|
||||
expectedErr bool
|
||||
expectedRes *corev1api.Pod
|
||||
restore *velerov1api.Restore
|
||||
}{
|
||||
{
|
||||
name: "should run restore hooks from pod annotation",
|
||||
restore: &velerov1api.Restore{},
|
||||
obj: builder.ForPod("default", "app1").
|
||||
ObjectMeta(builder.WithAnnotations(
|
||||
"init.hook.restore.velero.io/container-image", "nginx",
|
||||
"init.hook.restore.velero.io/container-name", "restore-init-container",
|
||||
"init.hook.restore.velero.io/command", `["a", "b", "c"]`,
|
||||
)).
|
||||
ServiceAccount("foo").
|
||||
Volumes([]*corev1api.Volume{{Name: "foo"}}...).
|
||||
InitContainers([]*corev1api.Container{
|
||||
builder.ForContainer("init-app-step1", "busy-box").
|
||||
Command([]string{"init-step1"}).Result(),
|
||||
builder.ForContainer("init-app-step2", "busy-box").
|
||||
Command([]string{"init-step2"}).Result(),
|
||||
builder.ForContainer("init-app-step3", "busy-box").
|
||||
Command([]string{"init-step3"}).Result()}...).Result(),
|
||||
expectedRes: builder.ForPod("default", "app1").
|
||||
ObjectMeta(builder.WithAnnotations(
|
||||
"init.hook.restore.velero.io/container-image", "nginx",
|
||||
"init.hook.restore.velero.io/container-name", "restore-init-container",
|
||||
"init.hook.restore.velero.io/command", `["a", "b", "c"]`,
|
||||
)).
|
||||
ServiceAccount("foo").
|
||||
Volumes([]*corev1api.Volume{{Name: "foo"}}...).
|
||||
InitContainers([]*corev1api.Container{
|
||||
builder.ForContainer("restore-init-container", "nginx").
|
||||
Command([]string{"a", "b", "c"}).Result(),
|
||||
builder.ForContainer("init-app-step1", "busy-box").
|
||||
Command([]string{"init-step1"}).Result(),
|
||||
builder.ForContainer("init-app-step2", "busy-box").
|
||||
Command([]string{"init-step2"}).Result(),
|
||||
builder.ForContainer("init-app-step3", "busy-box").
|
||||
Command([]string{"init-step3"}).Result()}...).Result(),
|
||||
},
|
||||
{
|
||||
name: "should run restore hook from restore spec",
|
||||
restore: &velerov1api.Restore{
|
||||
Spec: velerov1api.RestoreSpec{
|
||||
Hooks: velerov1api.RestoreHooks{
|
||||
Resources: []velerov1api.RestoreResourceHookSpec{
|
||||
{
|
||||
Name: "h1",
|
||||
IncludedNamespaces: []string{"default"},
|
||||
IncludedResources: []string{kuberesource.Pods.Resource},
|
||||
PostHooks: []velerov1api.RestoreResourceHook{
|
||||
{
|
||||
Init: &velerov1api.InitRestoreHook{
|
||||
InitContainers: []corev1api.Container{
|
||||
*builder.ForContainer("restore-init1", "busy-box").
|
||||
Command([]string{"foobarbaz"}).Result(),
|
||||
*builder.ForContainer("restore-init2", "busy-box").
|
||||
Command([]string{"foobarbaz"}).Result(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: builder.ForPod("default", "app1").
|
||||
ServiceAccount("foo").
|
||||
Volumes([]*corev1api.Volume{{Name: "foo"}}...).Result(),
|
||||
expectedRes: builder.ForPod("default", "app1").
|
||||
ServiceAccount("foo").
|
||||
Volumes([]*corev1api.Volume{{Name: "foo"}}...).
|
||||
InitContainers([]*corev1api.Container{
|
||||
builder.ForContainer("restore-init1", "busy-box").
|
||||
Command([]string{"foobarbaz"}).Result(),
|
||||
builder.ForContainer("restore-init2", "busy-box").
|
||||
Command([]string{"foobarbaz"}).Result(),
|
||||
}...).Result(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
action := NewInitRestoreHookPodAction(velerotest.NewLogger())
|
||||
unstructuredPod, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.obj)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := action.Execute(&velero.RestoreItemActionExecuteInput{
|
||||
Item: &unstructured.Unstructured{Object: unstructuredPod},
|
||||
ItemFromBackup: &unstructured.Unstructured{Object: unstructuredPod},
|
||||
Restore: tc.restore,
|
||||
})
|
||||
if tc.expectedErr {
|
||||
assert.NotNil(t, err, "expected an error")
|
||||
return
|
||||
}
|
||||
assert.Nil(t, err, "expected no error, got %v", err)
|
||||
|
||||
var pod corev1api.Pod
|
||||
require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(res.UpdatedItem.UnstructuredContent(), &pod))
|
||||
|
||||
assert.Equal(t, *tc.expectedRes, pod)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue