Skip to content

Commit ee34d53

Browse files
committed
Implement rewind command
1 parent f0b54c7 commit ee34d53

File tree

5 files changed

+328
-13
lines changed

5 files changed

+328
-13
lines changed

README.md

+29-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ workspace.
2020

2121
## Usage
2222

23+
All commands and options can be inspected by using `rv help`.
24+
2325
### Release new version
2426

2527
`rv` can be instructed to "release" a new version of the software
@@ -77,6 +79,31 @@ $ rv list -w /opt/workspace
7779
20240313151207.365
7880
```
7981

80-
## Rollback to an existing version
82+
## Rewind to an existing version
83+
84+
`rv` can be instructed to perform a rewind operation from the latest
85+
release to a previous target release. If no specific target is
86+
specified by the user, then the `current` link is set to the release
87+
that precedes the current one.
88+
89+
The rewind operation will **delete** all releases that were performed
90+
after the target release so as to maintain the integrity of the
91+
cleanup operations.
92+
93+
So, given:
94+
95+
```bash
96+
$ rv list -w /opt/workspace
97+
20240313151323.508 <== current
98+
20240313151207.365
99+
```
100+
101+
the rollback operation to the previous version can be achieved by:
81102

82-
TBD
103+
```bash
104+
$ rv rollback -w /opt/workspace
105+
[info] current=20240313151323.508
106+
[rewind] setting current to 20240313151207.365
107+
[cleanup] deleting 20240313151323.508
108+
[success] active version is 20240313151207.365
109+
```

cmd/common_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"os"
99
"strings"
10+
"time"
1011

1112
"github.com/google/uuid"
1213
"github.com/kkentzo/rv/release"
@@ -35,6 +36,31 @@ func createRelease(workspacePath, includedFile string, keepN uint) (string, erro
3536
return cmdOutput.String(), nil
3637
}
3738

39+
func createReleases(workspacePath string, n uint) ([]string, error) {
40+
releases := []string{}
41+
for i := 0; i < int(n); i++ {
42+
out, err := createRelease(workspacePath, "foo", n)
43+
if err != nil {
44+
return releases, err
45+
}
46+
releases = append(releases, parseReleaseFromOutput(out))
47+
time.Sleep(10 * time.Millisecond)
48+
}
49+
return releases, nil
50+
}
51+
52+
func rewindRelease(workspacePath, target string) (string, error) {
53+
// prepare command
54+
cmdOutput := createOutputBuffer(RootCmd)
55+
args := []string{"rewind", "-w", workspacePath, "-t", target}
56+
RootCmd.SetArgs(args)
57+
// FIRE!
58+
if err := RootCmd.Execute(); err != nil {
59+
return "", err
60+
}
61+
return cmdOutput.String(), nil
62+
}
63+
3864
func parseReleaseFromOutput(out string) string {
3965
for _, line := range strings.Split(out, "\n") {
4066
if strings.HasPrefix(line, "[success]") {

cmd/rewind.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/kkentzo/rv/release"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
// rewindCmd represents the rewind command
11+
var (
12+
// command-line arguments
13+
target string
14+
// command
15+
rewindCmdDescription = "Reset the current release"
16+
RewindCmd = &cobra.Command{
17+
Use: "rewind",
18+
Short: rewindCmdDescription,
19+
Long: rewindCmdDescription,
20+
Run: func(cmd *cobra.Command, args []string) {
21+
// perform release
22+
releaseID, err := release.Rewind(globalWorkspacePath, target, cmd.OutOrStdout())
23+
if err != nil {
24+
fmt.Fprintf(cmd.OutOrStderr(), "error: %v\n", err)
25+
} else {
26+
fmt.Fprintf(cmd.OutOrStdout(), "[success] active version is %s\n", releaseID)
27+
}
28+
},
29+
}
30+
)
31+
32+
func init() {
33+
requireWorkspaceFlag(RewindCmd)
34+
RewindCmd.Flags().StringVarP(&target, "target", "t", "", "target release to reset the current link to")
35+
RootCmd.AddCommand(RewindCmd)
36+
}

cmd/rewind_test.go

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
"path"
6+
"testing"
7+
8+
"github.com/google/uuid"
9+
"github.com/kkentzo/rv/release"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func Test_Rewind_NoTarget_ShouldResetCurrentAndDeletePreviousCurrent(t *testing.T) {
15+
workspacePath := uuid.NewString()
16+
defer os.RemoveAll(workspacePath)
17+
18+
releases, err := createReleases(workspacePath, 3)
19+
require.Len(t, releases, 3)
20+
21+
// verify the current release
22+
current, err := release.GetCurrent(workspacePath)
23+
assert.NoError(t, err)
24+
assert.Equal(t, releases[2], current)
25+
26+
// ok, let's rewind now with no target
27+
_, err = rewindRelease(workspacePath, "")
28+
require.NoError(t, err)
29+
30+
// verify the remaining releases
31+
assert.DirExists(t, path.Join(workspacePath, releases[0]))
32+
assert.DirExists(t, path.Join(workspacePath, releases[1]))
33+
assert.NoDirExists(t, path.Join(workspacePath, releases[2]))
34+
35+
// verify the current link
36+
current, err = release.GetCurrent(workspacePath)
37+
assert.NoError(t, err)
38+
assert.Equal(t, releases[1], current)
39+
}
40+
41+
func Test_Rewind_WithTarget_ShouldResetCurrentAndDeletePreviousCurrent(t *testing.T) {
42+
workspacePath := uuid.NewString()
43+
defer os.RemoveAll(workspacePath)
44+
45+
releases, err := createReleases(workspacePath, 3)
46+
require.Len(t, releases, 3)
47+
48+
// verify the current release
49+
current, err := release.GetCurrent(workspacePath)
50+
assert.NoError(t, err)
51+
assert.Equal(t, releases[2], current)
52+
53+
// ok, let's rewind now
54+
_, err = rewindRelease(workspacePath, releases[0])
55+
require.NoError(t, err)
56+
57+
// verify the remaining releases
58+
assert.DirExists(t, path.Join(workspacePath, releases[0]))
59+
assert.NoDirExists(t, path.Join(workspacePath, releases[1]))
60+
assert.NoDirExists(t, path.Join(workspacePath, releases[2]))
61+
62+
// verify the current link
63+
current, err = release.GetCurrent(workspacePath)
64+
assert.NoError(t, err)
65+
assert.Equal(t, releases[0], current)
66+
}
67+
68+
func Test_Rewind_WhenTheTargetDoesNotExist(t *testing.T) {
69+
workspacePath := uuid.NewString()
70+
defer os.RemoveAll(workspacePath)
71+
72+
releases, err := createReleases(workspacePath, 3)
73+
require.Len(t, releases, 3)
74+
75+
// verify the current release
76+
current, err := release.GetCurrent(workspacePath)
77+
assert.NoError(t, err)
78+
assert.Equal(t, releases[2], current)
79+
80+
// ok, let's rewind now with no target
81+
out, err := rewindRelease(workspacePath, "a_non_existent_target")
82+
assert.NoError(t, err)
83+
assert.Contains(t, out, "a_non_existent_target not found")
84+
85+
assert.DirExists(t, path.Join(workspacePath, releases[0]))
86+
assert.DirExists(t, path.Join(workspacePath, releases[1]))
87+
assert.DirExists(t, path.Join(workspacePath, releases[2]))
88+
89+
// verify the current link
90+
current, err = release.GetCurrent(workspacePath)
91+
assert.NoError(t, err)
92+
assert.Equal(t, releases[2], current)
93+
}
94+
95+
func Test_Rewind_WhenThereIsOnlyOneRelease(t *testing.T) {
96+
workspacePath := uuid.NewString()
97+
defer os.RemoveAll(workspacePath)
98+
99+
releases, err := createReleases(workspacePath, 1)
100+
require.Len(t, releases, 1)
101+
102+
// verify the current link
103+
current, err := release.GetCurrent(workspacePath)
104+
assert.NoError(t, err)
105+
assert.Equal(t, releases[0], current)
106+
107+
// ok, let's rewind now
108+
out, err := rewindRelease(workspacePath, "")
109+
assert.NoError(t, err)
110+
assert.Contains(t, out, "only one release in workspace")
111+
112+
assert.DirExists(t, path.Join(workspacePath, releases[0]))
113+
114+
// verify the current link
115+
current, err = release.GetCurrent(workspacePath)
116+
assert.NoError(t, err)
117+
assert.Equal(t, releases[0], current)
118+
}
119+
120+
func Test_Rewind_WhenTargetIsAlreadyCurrent(t *testing.T) {
121+
workspacePath := uuid.NewString()
122+
defer os.RemoveAll(workspacePath)
123+
124+
releases, err := createReleases(workspacePath, 2)
125+
require.Len(t, releases, 2)
126+
127+
// verify the current release
128+
current, err := release.GetCurrent(workspacePath)
129+
assert.NoError(t, err)
130+
assert.Equal(t, releases[1], current)
131+
132+
// ok, let's rewind now with no target
133+
out, err := rewindRelease(workspacePath, releases[1])
134+
assert.NoError(t, err)
135+
assert.Contains(t, out, "will not rewind")
136+
137+
assert.DirExists(t, path.Join(workspacePath, releases[0]))
138+
assert.DirExists(t, path.Join(workspacePath, releases[1]))
139+
140+
// verify the current link
141+
current, err = release.GetCurrent(workspacePath)
142+
assert.NoError(t, err)
143+
assert.Equal(t, releases[1], current)
144+
}

0 commit comments

Comments
 (0)