Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create backups from schedules using velero create backup #1734

Merged
1 change: 1 addition & 0 deletions changelogs/unreleased/1734-prydonius
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
adds --from-schedule flag to the `velero create backup` command to create a Backup from an existing Schedule
19 changes: 13 additions & 6 deletions pkg/builder/backup_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ func (b *BackupBuilder) ObjectMeta(opts ...ObjectMetaOpt) *BackupBuilder {
return b
}

// FromSchedule sets the Backup's spec and labels from the Schedule template
func (b *BackupBuilder) FromSchedule(schedule *velerov1api.Schedule) *BackupBuilder {
labels := schedule.Labels
if labels == nil {
labels = make(map[string]string)
}
labels[velerov1api.ScheduleNameLabel] = schedule.Name

b.object.Spec = schedule.Spec.Template
b.ObjectMeta(WithLabelsMap(labels))
return b
}

// IncludedNamespaces sets the Backup's included namespaces.
func (b *BackupBuilder) IncludedNamespaces(namespaces ...string) *BackupBuilder {
b.object.Spec.IncludedNamespaces = namespaces
Expand Down Expand Up @@ -151,12 +164,6 @@ func (b *BackupBuilder) StartTimestamp(val time.Time) *BackupBuilder {
return b
}

// NoTypeMeta removes the type meta from the Backup.
func (b *BackupBuilder) NoTypeMeta() *BackupBuilder {
b.object.TypeMeta = metav1.TypeMeta{}
return b
}

// Hooks sets the Backup's hooks.
func (b *BackupBuilder) Hooks(hooks velerov1api.BackupHooks) *BackupBuilder {
b.object.Spec.Hooks = hooks
Expand Down
19 changes: 19 additions & 0 deletions pkg/builder/object_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ func WithLabels(vals ...string) func(obj metav1.Object) {
}
}

// WithLabelsMap is a functional option that applies the specified labels map to
// an object.
func WithLabelsMap(labels map[string]string) func(obj metav1.Object) {
return func(obj metav1.Object) {
objLabels := obj.GetLabels()
if objLabels == nil {
objLabels = make(map[string]string)
}

// If the label already exists in the object, it will be overwritten
for k, v := range labels {
objLabels[k] = v
skriss marked this conversation as resolved.
Show resolved Hide resolved
}

obj.SetLabels(objLabels)
}
}

// WithAnnotations is a functional option that applies the specified
// annotation keys/values to an object.
func WithAnnotations(vals ...string) func(obj metav1.Object) {
Expand All @@ -66,6 +84,7 @@ func setMapEntries(m map[string]string, vals ...string) map[string]string {
key := vals[i]
val := vals[i+1]

// If the label already exists in the object, it will be overwritten
m[key] = val
}

Expand Down
64 changes: 45 additions & 19 deletions pkg/cmd/cli/backup/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package backup

import (
"errors"
"fmt"
"time"

Expand All @@ -26,6 +27,7 @@ import (
"k8s.io/client-go/tools/cache"

api "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"
Expand All @@ -34,6 +36,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()

Expand Down Expand Up @@ -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),
Expand All @@ -108,6 +113,7 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
flags.StringVar(&o.StorageLocation, "storage-location", "", "location in which to store the backup")
flags.StringSliceVar(&o.SnapshotLocations, "volume-snapshot-locations", o.SnapshotLocations, "list of locations (at most one per provider) where volume snapshots should be stored")
flags.VarP(&o.Selector, "selector", "l", "only back up resources matching this label selector")
flags.StringVar(&o.FromSchedule, "from-schedule", "", "create a backup from a Schedule. Cannot be used with any other filters.")
f := flags.VarPF(&o.SnapshotVolumes, "snapshot-volumes", "", "take snapshots of PersistentVolumes as part of the backup")
// this allows the user to just specify "--snapshot-volumes" as shorthand for "--snapshot-volumes=true"
// like a normal bool flag
Expand All @@ -128,6 +134,17 @@ func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Facto
return err
}

if o.FromSchedule != "" {
if (len(o.IncludeNamespaces) > 1 || o.IncludeNamespaces[0] != "*") ||
skriss marked this conversation as resolved.
Show resolved Hide resolved
len(o.ExcludeNamespaces) > 0 || len(o.IncludeResources) > 0 ||
len(o.ExcludeResources) > 0 || o.Selector.LabelSelector != nil ||
o.SnapshotVolumes.Value != nil || o.TTL != DefaultBackupTTL ||
o.IncludeClusterResources.Value != nil ||
o.StorageLocation != "" || len(o.SnapshotLocations) > 0 {
return errors.New("backup filters cannot be set when creating a Backup from a Schedule")
}
}

if o.StorageLocation != "" {
if _, err := o.client.VeleroV1().BackupStorageLocations(f.Namespace()).Get(o.StorageLocation, metav1.GetOptions{}); err != nil {
return err
Expand All @@ -154,26 +171,35 @@ 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,
},
backupBuilder := builder.ForBackup(f.Namespace(), o.Name)

if o.FromSchedule != "" {
skriss marked this conversation as resolved.
Show resolved Hide resolved
schedule, err := o.client.VeleroV1().Schedules(f.Namespace()).Get(o.FromSchedule, metav1.GetOptions{})
if err != nil {
return 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()

if printed, err := output.PrintWithFormat(c, backup); printed || err != nil {
return err
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/cmd/cli/schedule/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ func NewCreateOptions() *CreateOptions {

func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
o.BackupOptions.BindFlags(flags)
// hide the Backup options --from-schedule flag
// TODO: see if there is a way to remove the flag
flags.MarkHidden("from-schedule")
skriss marked this conversation as resolved.
Show resolved Hide resolved
flags.StringVar(&o.Schedule, "schedule", o.Schedule, "a cron expression specifying a recurring schedule for this backup to run")
}

Expand Down
26 changes: 6 additions & 20 deletions pkg/controller/schedule_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
"k8s.io/client-go/tools/cache"

api "github.com/heptio/velero/pkg/apis/velero/v1"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skriss looks like this used to have a duplicate package import too, should I update this to use velerov1api everywhere too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, let's get this one merged - we can update this next time we touch this file.

velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
"github.com/heptio/velero/pkg/builder"
velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
informers "github.com/heptio/velero/pkg/generated/informers/externalversions/velero/v1"
listers "github.com/heptio/velero/pkg/generated/listers/velero/v1"
Expand Down Expand Up @@ -286,29 +286,15 @@ func getNextRunTime(schedule *api.Schedule, cronSchedule cron.Schedule, asOf tim
}

func getBackup(item *api.Schedule, timestamp time.Time) *api.Backup {
backup := &api.Backup{
Spec: item.Spec.Template,
ObjectMeta: metav1.ObjectMeta{
Namespace: item.Namespace,
Name: fmt.Sprintf("%s-%s", item.Name, timestamp.Format("20060102150405")),
},
}

addLabelsToBackup(item, backup)
name := fmt.Sprintf("%s-%s", item.Name, timestamp.Format("20060102150405"))
backup := builder.
ForBackup(item.Namespace, name).
FromSchedule(item).
Result()

return backup
}

func addLabelsToBackup(item *api.Schedule, backup *api.Backup) {
labels := item.Labels
if labels == nil {
labels = make(map[string]string)
}
labels[velerov1api.ScheduleNameLabel] = item.Name

backup.Labels = labels
}

func patchSchedule(original, updated *api.Schedule, client velerov1client.SchedulesGetter) (*api.Schedule, error) {
origBytes, err := json.Marshal(original)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions pkg/controller/schedule_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,23 +97,23 @@ func TestProcessSchedule(t *testing.T) {
fakeClockTime: "2017-01-01 12:00:00",
expectedErr: false,
expectedPhase: string(velerov1api.SchedulePhaseEnabled),
expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "name")).NoTypeMeta().Result(),
skriss marked this conversation as resolved.
Show resolved Hide resolved
expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "name")).Result(),
expectedLastBackup: "2017-01-01 12:00:00",
},
{
name: "schedule with phase Enabled gets re-validated and triggers a backup if valid",
schedule: newScheduleBuilder(velerov1api.SchedulePhaseEnabled).CronSchedule("@every 5m").Result(),
fakeClockTime: "2017-01-01 12:00:00",
expectedErr: false,
expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "name")).NoTypeMeta().Result(),
expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "name")).Result(),
expectedLastBackup: "2017-01-01 12:00:00",
},
{
name: "schedule that's already run gets LastBackup updated",
schedule: newScheduleBuilder(velerov1api.SchedulePhaseEnabled).CronSchedule("@every 5m").LastBackupTime("2000-01-01 00:00:00").Result(),
fakeClockTime: "2017-01-01 12:00:00",
expectedErr: false,
expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "name")).NoTypeMeta().Result(),
expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "name")).Result(),
expectedLastBackup: "2017-01-01 12:00:00",
},
}
Expand Down