mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-08 22:23:15 +00:00
create backups from schedules using velero create backup (#1734)
* add --from-schedule to `velero backup create` to create backups from schedules Signed-off-by: Adnan Abdulhussein <aadnan@vmware.com>
This commit is contained in:
committed by
KubeKween
parent
686f41ebec
commit
6aa0215137
@@ -25,7 +25,8 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
api "github.com/heptio/velero/pkg/apis/velero/v1"
|
||||
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
|
||||
"github.com/heptio/velero/pkg/builder"
|
||||
"github.com/heptio/velero/pkg/client"
|
||||
"github.com/heptio/velero/pkg/cmd"
|
||||
"github.com/heptio/velero/pkg/cmd/util/flag"
|
||||
@@ -34,6 +35,8 @@ import (
|
||||
v1 "github.com/heptio/velero/pkg/generated/informers/externalversions/velero/v1"
|
||||
)
|
||||
|
||||
const DefaultBackupTTL time.Duration = 30 * 24 * time.Hour
|
||||
|
||||
func NewCreateCommand(f client.Factory, use string) *cobra.Command {
|
||||
o := NewCreateOptions()
|
||||
|
||||
@@ -64,6 +67,7 @@ func NewCreateCommand(f client.Factory, use string) *cobra.Command {
|
||||
|
||||
o.BindFlags(c.Flags())
|
||||
o.BindWait(c.Flags())
|
||||
o.BindFromSchedule(c.Flags())
|
||||
output.BindFlags(c.Flags())
|
||||
output.ClearOutputFlagDefault(c)
|
||||
|
||||
@@ -84,13 +88,14 @@ type CreateOptions struct {
|
||||
Wait bool
|
||||
StorageLocation string
|
||||
SnapshotLocations []string
|
||||
FromSchedule string
|
||||
|
||||
client veleroclient.Interface
|
||||
}
|
||||
|
||||
func NewCreateOptions() *CreateOptions {
|
||||
return &CreateOptions{
|
||||
TTL: 30 * 24 * time.Hour,
|
||||
TTL: DefaultBackupTTL,
|
||||
IncludeNamespaces: flag.NewStringArray("*"),
|
||||
Labels: flag.NewMap(),
|
||||
SnapshotVolumes: flag.NewOptionalBool(nil),
|
||||
@@ -123,6 +128,12 @@ func (o *CreateOptions) BindWait(flags *pflag.FlagSet) {
|
||||
flags.BoolVarP(&o.Wait, "wait", "w", o.Wait, "wait for the operation to complete")
|
||||
}
|
||||
|
||||
// BindFromSchedule binds the from-schedule flag separately so it is not called
|
||||
// by other create commands that reuse CreateOptions's BindFlags method.
|
||||
func (o *CreateOptions) BindFromSchedule(flags *pflag.FlagSet) {
|
||||
flags.StringVar(&o.FromSchedule, "from-schedule", "", "create a backup from the template of an existing schedule. Cannot be used with any other filters.")
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Factory) error {
|
||||
if err := output.ValidateFlags(c); err != nil {
|
||||
return err
|
||||
@@ -154,44 +165,33 @@ func (o *CreateOptions) Complete(args []string, f client.Factory) error {
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
backup := &api.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: f.Namespace(),
|
||||
Name: o.Name,
|
||||
Labels: o.Labels.Data(),
|
||||
},
|
||||
Spec: api.BackupSpec{
|
||||
IncludedNamespaces: o.IncludeNamespaces,
|
||||
ExcludedNamespaces: o.ExcludeNamespaces,
|
||||
IncludedResources: o.IncludeResources,
|
||||
ExcludedResources: o.ExcludeResources,
|
||||
LabelSelector: o.Selector.LabelSelector,
|
||||
SnapshotVolumes: o.SnapshotVolumes.Value,
|
||||
TTL: metav1.Duration{Duration: o.TTL},
|
||||
IncludeClusterResources: o.IncludeClusterResources.Value,
|
||||
StorageLocation: o.StorageLocation,
|
||||
VolumeSnapshotLocations: o.SnapshotLocations,
|
||||
},
|
||||
backup, err := o.BuildBackup(f.Namespace())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if printed, err := output.PrintWithFormat(c, backup); printed || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.FromSchedule != "" {
|
||||
fmt.Println("Creating backup from schedule, all other filters are ignored.")
|
||||
}
|
||||
|
||||
var backupInformer cache.SharedIndexInformer
|
||||
var updates chan *api.Backup
|
||||
var updates chan *velerov1api.Backup
|
||||
if o.Wait {
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
|
||||
updates = make(chan *api.Backup)
|
||||
updates = make(chan *velerov1api.Backup)
|
||||
|
||||
backupInformer = v1.NewBackupInformer(o.client, f.Namespace(), 0, nil)
|
||||
|
||||
backupInformer.AddEventHandler(
|
||||
cache.FilteringResourceEventHandler{
|
||||
FilterFunc: func(obj interface{}) bool {
|
||||
backup, ok := obj.(*api.Backup)
|
||||
backup, ok := obj.(*velerov1api.Backup)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -199,14 +199,14 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
},
|
||||
Handler: cache.ResourceEventHandlerFuncs{
|
||||
UpdateFunc: func(_, obj interface{}) {
|
||||
backup, ok := obj.(*api.Backup)
|
||||
backup, ok := obj.(*velerov1api.Backup)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
updates <- backup
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
backup, ok := obj.(*api.Backup)
|
||||
backup, ok := obj.(*velerov1api.Backup)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -218,7 +218,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
go backupInformer.Run(stop)
|
||||
}
|
||||
|
||||
_, err := o.client.VeleroV1().Backups(backup.Namespace).Create(backup)
|
||||
_, err = o.client.VeleroV1().Backups(backup.Namespace).Create(backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -239,7 +239,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if backup.Status.Phase != api.BackupPhaseNew && backup.Status.Phase != api.BackupPhaseInProgress {
|
||||
if backup.Status.Phase != velerov1api.BackupPhaseNew && backup.Status.Phase != velerov1api.BackupPhaseInProgress {
|
||||
fmt.Printf("\nBackup completed with status: %s. You may check for more information using the commands `velero backup describe %s` and `velero backup logs %s`.\n", backup.Status.Phase, backup.Name, backup.Name)
|
||||
return nil
|
||||
}
|
||||
@@ -253,3 +253,35 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) BuildBackup(namespace string) (*velerov1api.Backup, error) {
|
||||
backupBuilder := builder.ForBackup(namespace, o.Name)
|
||||
|
||||
if o.FromSchedule != "" {
|
||||
schedule, err := o.client.VeleroV1().Schedules(namespace).Get(o.FromSchedule, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backupBuilder.FromSchedule(schedule)
|
||||
} else {
|
||||
backupBuilder.
|
||||
IncludedNamespaces(o.IncludeNamespaces...).
|
||||
ExcludedNamespaces(o.ExcludeNamespaces...).
|
||||
IncludedResources(o.IncludeResources...).
|
||||
ExcludedResources(o.ExcludeResources...).
|
||||
LabelSelector(o.Selector.LabelSelector).
|
||||
TTL(o.TTL).
|
||||
StorageLocation(o.StorageLocation).
|
||||
VolumeSnapshotLocations(o.SnapshotLocations...)
|
||||
|
||||
if o.SnapshotVolumes.Value != nil {
|
||||
backupBuilder.SnapshotVolumes(*o.SnapshotVolumes.Value)
|
||||
}
|
||||
if o.IncludeClusterResources.Value != nil {
|
||||
backupBuilder.IncludeClusterResources(*o.IncludeClusterResources.Value)
|
||||
}
|
||||
}
|
||||
|
||||
backup := backupBuilder.ObjectMeta(builder.WithLabelsMap(o.Labels.Data())).Result()
|
||||
return backup, nil
|
||||
}
|
||||
|
||||
88
pkg/cmd/cli/backup/create_test.go
Normal file
88
pkg/cmd/cli/backup/create_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright 2019 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 backup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
|
||||
"github.com/heptio/velero/pkg/builder"
|
||||
"github.com/heptio/velero/pkg/generated/clientset/versioned/fake"
|
||||
)
|
||||
|
||||
const testNamespace = "velero"
|
||||
|
||||
func TestCreateOptions_BuildBackup(t *testing.T) {
|
||||
o := NewCreateOptions()
|
||||
o.Labels.Set("velero.io/test=true")
|
||||
|
||||
backup, err := o.BuildBackup(testNamespace)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, velerov1api.BackupSpec{
|
||||
TTL: metav1.Duration{Duration: o.TTL},
|
||||
IncludedNamespaces: []string(o.IncludeNamespaces),
|
||||
SnapshotVolumes: o.SnapshotVolumes.Value,
|
||||
IncludeClusterResources: o.IncludeClusterResources.Value,
|
||||
}, backup.Spec)
|
||||
|
||||
assert.Equal(t, map[string]string{
|
||||
"velero.io/test": "true",
|
||||
}, backup.GetLabels())
|
||||
}
|
||||
|
||||
func TestCreateOptions_BuildBackupFromSchedule(t *testing.T) {
|
||||
o := NewCreateOptions()
|
||||
o.FromSchedule = "test"
|
||||
o.client = fake.NewSimpleClientset()
|
||||
|
||||
t.Run("inexistant schedule", func(t *testing.T) {
|
||||
_, err := o.BuildBackup(testNamespace)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
expectedBackupSpec := builder.ForBackup("test", testNamespace).IncludedNamespaces("test").Result().Spec
|
||||
schedule := builder.ForSchedule(testNamespace, "test").Template(expectedBackupSpec).ObjectMeta(builder.WithLabels("velero.io/test", "true")).Result()
|
||||
o.client.VeleroV1().Schedules(testNamespace).Create(schedule)
|
||||
|
||||
t.Run("existing schedule", func(t *testing.T) {
|
||||
backup, err := o.BuildBackup(testNamespace)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedBackupSpec, backup.Spec)
|
||||
assert.Equal(t, map[string]string{
|
||||
"velero.io/test": "true",
|
||||
velerov1api.ScheduleNameLabel: "test",
|
||||
}, backup.GetLabels())
|
||||
})
|
||||
|
||||
t.Run("command line labels take precedence over schedule labels", func(t *testing.T) {
|
||||
o.Labels.Set("velero.io/test=yes,custom-label=true")
|
||||
backup, err := o.BuildBackup(testNamespace)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedBackupSpec, backup.Spec)
|
||||
assert.Equal(t, map[string]string{
|
||||
"velero.io/test": "yes",
|
||||
velerov1api.ScheduleNameLabel: "test",
|
||||
"custom-label": "true",
|
||||
}, backup.GetLabels())
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user