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

Add --latest flag to restore-snapshot command, fixes #1886, fixes #1163 #2724

Merged
merged 15 commits into from
Jan 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 6 additions & 21 deletions cmd/ddev/cmd/restore_snapshot.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,20 @@
package cmd

import (
"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/util"
"github.com/spf13/cobra"
"os"
)

// DdevRestoreSnapshotCommand provides the ability to revert to a database snapshot
var DdevRestoreSnapshotCommand = &cobra.Command{
Use: "restore-snapshot [snapshot_name]",
Short: "Restore a project's database to the provided snapshot version.",
Long: `Uses mariabackup command to restore a project database to a particular snapshot from the .ddev/db_snapshots folder.
Example: "ddev restore-snapshot d8git_20180717203845"`,
Hidden: true,
Use: "restore-snapshot [snapshot_name]",
Short: "Restore a project's database to the provided snapshot version.",
Long: "Please use \"snapshot restore\" command",
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
util.Warning("Please provide the name of the snapshot you want to restore." +
"\nThe available snapshots are in .ddev/db_snapshots.")
_ = cmd.Usage()
os.Exit(1)
}

snapshotName := args[0]
app, err := ddevapp.GetActiveApp("")
if err != nil {
util.Failed("Failed to find active project: %v", err)
}

if err := app.RestoreSnapshot(snapshotName); err != nil {
util.Failed("Failed to restore snapshot %s for project %s: %v", snapshotName, app.GetName(), err)
}
util.Failed("Please use \"ddev snapshot restore\".")
os.Exit(1)
},
}

Expand Down
3 changes: 2 additions & 1 deletion cmd/ddev/cmd/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var snapshotAll bool
var snapshotCleanup bool
var snapshotList bool
var snapshotName string
var snapshotRestoreLatest bool

// noConfirm: If true, --yes, we won't stop and prompt before each deletion
var snapshotCleanupNoConfirm bool
Expand All @@ -20,7 +21,7 @@ var snapshotCleanupNoConfirm bool
var DdevSnapshotCommand = &cobra.Command{
Use: "snapshot [projectname projectname...]",
Short: "Create a database snapshot for one or more projects.",
Long: `Uses mariabackup or xtrabackup command to create a database snapshot in the .ddev/db_snapshots folder. These are compatible with server backups using the same tools and can be restored with "ddev restore-snapshot".`,
Long: `Uses mariabackup or xtrabackup command to create a database snapshot in the .ddev/db_snapshots folder. These are compatible with server backups using the same tools and can be restored with "ddev snapshot restore".`,
Example: `ddev snapshot
ddev snapshot --name some_descriptive_name
ddev snapshot --cleanup
Expand Down
72 changes: 72 additions & 0 deletions cmd/ddev/cmd/snapshot_restore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package cmd

import (
"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/nodeps"
"github.com/drud/ddev/pkg/util"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
)

var DdevSnapshotRestoreCommand = &cobra.Command{
Use: "restore [snapshot_name]",
Short: "Restore a project's database to the provided snapshot version.",
Long: `Uses mariabackup command to restore a project database to a particular snapshot from the .ddev/db_snapshots folder.
Example: "ddev snapshot restore d8git_20180717203845"`,
Run: func(cmd *cobra.Command, args []string) {
var snapshotName string

app, err := ddevapp.GetActiveApp("")
if err != nil {
util.Failed("Failed to find active project: %v", err)
}

if snapshotRestoreLatest {
if snapshotName, err = app.GetLatestSnapshot(); err != nil {
util.Failed("Failed to get latest snapshot of project %s: %v", app.GetName(), err)
}
} else {
if len(args) != 1 {

snapshots, err := app.ListSnapshots()
if err != nil {
util.Failed("Cannot list snapshots of project %s: %v", app.GetName(), err)
}

if len(snapshots) == 0 {
util.Failed("No snapshots found for project %s", app.GetName())
}

templates := &promptui.SelectTemplates{
Label: "{{ . | cyan }}:",
}

prompt := promptui.Select{
Label: "Snapshot",
Items: snapshots,
Templates: templates,
}

_, snapshotName, err = prompt.Run()

if err != nil {
util.Failed("Prompt failed %v", err)
}
} else {
snapshotName = args[0]
}
}

if err := app.RestoreSnapshot(snapshotName); err != nil {
util.Failed("Failed to restore snapshot %s for project %s: %v", snapshotName, app.GetName(), err)
}
},
}

func init() {
app, err := ddevapp.GetActiveApp("")
if err == nil && app != nil && !nodeps.ArrayContainsString(app.OmitContainers, "db") {
DdevSnapshotRestoreCommand.Flags().BoolVarP(&snapshotRestoreLatest, "latest", "", false, "use latest snapshot")
DdevSnapshotCommand.AddCommand(DdevSnapshotRestoreCommand)
}
}
55 changes: 55 additions & 0 deletions cmd/ddev/cmd/snapshot_restore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cmd

import (
"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/exec"
asrt "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"runtime"
"testing"
)

// TestCmdSnapshotRestore runs `ddev snapshot restore` on the test apps
func TestCmdSnapshotRestore(t *testing.T) {
assert := asrt.New(t)

site := TestSites[0]
cleanup := site.Chdir()

app, err := ddevapp.NewApp(site.Dir, false, "")
assert.NoError(err)

t.Cleanup(func() {
// Make sure all databases are back to default empty
_ = app.Stop(true, false)
cleanup()
})

err = app.Start()
require.NoError(t, err)

// Ensure that a snapshot is created
args := []string{"snapshot", "--name", "test-snapshot"}
out, err := exec.RunCommand(DdevBin, args)
assert.NoError(err)
assert.Contains(out, "Created snapshot test-snapshot")

// Ensure that a snapshot can be restored
args = []string{"snapshot", "restore", "test-snapshot"}
out, err = exec.RunCommand(DdevBin, args)
assert.NoError(err)
assert.Contains(out, "Restored database snapshot")

// Try interactive command
if runtime.GOOS != "windows" {
out, err = exec.RunCommand("bash", []string{"-c", "echo -nq '\n' | " + DdevBin + " snapshot restore"})
assert.NoError(err)
assert.Contains(out, "Restored database snapshot")
}

// Ensure that latest snapshot can be restored
args = []string{"snapshot", "restore", "--latest"}
out, err = exec.RunCommand(DdevBin, args)
assert.NoError(err)
assert.Contains(out, "Restored database snapshot")
}
30 changes: 18 additions & 12 deletions cmd/ddev/cmd/snapshot_test.go
Original file line number Diff line number Diff line change
@@ -1,46 +1,52 @@
package cmd

import (
"fmt"
"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/exec"
asrt "github.com/stretchr/testify/assert"
"os"
"github.com/stretchr/testify/require"
"testing"
)

// TestCmdSnapshot runs `ddev snapshot` on the test apps
func TestCmdSnapshot(t *testing.T) {
assert := asrt.New(t)

testDir, _ := os.Getwd()
fmt.Println(testDir)
site := TestSites[0]
cleanup := site.Chdir()

app, err := ddevapp.NewApp(site.Dir, false, "")
assert.NoError(err)
defer func() {

t.Cleanup(func() {
// Make sure all databases are back to default empty
_ = app.Stop(true, false)
_ = app.Start()
cleanup()
}()
})

err = app.Start()
require.NoError(t, err)

// Ensure that there are no snapshots available before we create one
args := []string{"snapshot", "--cleanup", "--yes"}
_, err = exec.RunCommand(DdevBin, args)
assert.NoError(err)

// Ensure that a snapshot can be created
args := []string{"snapshot", "--name", "test-snapshot"}
args = []string{"snapshot", "--name", "test-snapshot"}
out, err := exec.RunCommand(DdevBin, args)
assert.NoError(err)
assert.Contains(string(out), "Created snapshot test-snapshot")
assert.Contains(out, "Created snapshot test-snapshot")

// Try to delete a not existing snapshot
args = []string{"snapshot", "--name", "not-existing-snapshot", "--cleanup", "--yes"}
out, err = exec.RunCommand(DdevBin, args)
assert.Error(err)
assert.Contains(string(out), "Failed to delete snapshot")
assert.Contains(out, "Failed to delete snapshot")

// Ensure that an existing snapshot can be deleted
args = []string{"snapshot", "--name", "test-snapshot", "--cleanup"}
args = []string{"snapshot", "--name", "test-snapshot", "--cleanup", "--yes"}
out, err = exec.RunCommand(DdevBin, args)
assert.NoError(err)
assert.Contains(string(out), "Deleted database snapshot test-snapshot")
assert.Contains(out, "Deleted database snapshot test-snapshot")
cmuench marked this conversation as resolved.
Show resolved Hide resolved
}
5 changes: 3 additions & 2 deletions docs/users/cli-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -665,11 +665,12 @@ Creating database snapshot d8git_20180801132403
Created database snapshot d8git_20180801132403 in /Users/rfay/workspace/d8git/.ddev/db_snapshots/d8git_20180801132403
Created snapshot d8git_20180801132403

ddev restore-snapshot d8git_20180801132403
ddev snapshot restore d8git_20180801132403
Restored database snapshot: /Users/rfay/workspace/d8git/.ddev/db_snapshots/d8git_20180801132403
```
cmuench marked this conversation as resolved.
Show resolved Hide resolved

Snapshots are stored in the project's .ddev/db_snapshots directory, and the directory can be renamed as necessary. For example, if you rename the above d8git_20180801132403 directory to "working_before_migration", then you can use `ddev restore-snapshot working_before_migration`.
Snapshots are stored in the project's .ddev/db_snapshots directory, and the directory can be renamed as necessary. For example, if you rename the above d8git_20180801132403 directory to "working_before_migration", then you can use `ddev snapshot restore working_before_migration`.
To restore the latest snapshot add the `--latest` flag (`ddev snapshot restore --latest`).

To delete a snapshot, delete its folder from the .ddev/db_snapshots directory. Snapshots are not removed from the filesystem by `ddev delete`. It is safe to remove all the snapshots with `rm -r .ddev/db_snapshots` if you no longer need the snapshots.

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ require (
github.com/imdario/mergo v0.3.5 // indirect
github.com/lextoumbourou/goodhosts v2.1.0+incompatible
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a
github.com/mattn/go-isatty v0.0.3
github.com/manifoldco/promptui v0.8.0
github.com/mattn/go-isatty v0.0.4
github.com/mattn/go-runewidth v0.0.2 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/pelletier/go-toml v1.4.0 // indirect
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cheggaaa/pb v1.0.25 h1:tFpebHTkI7QZx1q1rWGOKhbunhZ3fMaxTvHDWn1bH/4=
github.com/cheggaaa/pb v1.0.25/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
Expand Down Expand Up @@ -194,6 +198,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
Expand All @@ -217,10 +223,14 @@ github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDe
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo=
github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
Expand Down Expand Up @@ -414,6 +424,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
24 changes: 24 additions & 0 deletions pkg/ddevapp/ddevapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"

Expand Down Expand Up @@ -1557,6 +1558,22 @@ func (app *DdevApp) DeleteSnapshot(snapshotName string) error {

}

// GetLatestSnapshot returns the latest created snapshot of a project
func (app *DdevApp) GetLatestSnapshot() (string, error) {
var snapshots []string

snapshots, err := app.ListSnapshots()
if err != nil {
return "", err
}

if len(snapshots) == 0 {
return "", fmt.Errorf("no snapshots found")
}

return snapshots[0], nil
}

// ListSnapshots returns a list of the names of all project snapshots
func (app *DdevApp) ListSnapshots() ([]string, error) {
var err error
Expand All @@ -1573,6 +1590,13 @@ func (app *DdevApp) ListSnapshots() ([]string, error) {
return snapshots, err
}

// Sort snapshots by last modification time
// we need that to detect the latest snapshot
// first snapshot is the latest
sort.Slice(files, func(i, j int) bool {
return files[i].ModTime().After(files[j].ModTime())
})

for _, f := range files {
if f.IsDir() {
snapshots = append(snapshots, f.Name())
Expand Down
Loading