Skip to content

Commit f16819c

Browse files
authored
Allow for nonlinear interval (without testify lib) (#33)
* Add ability to do nonlinear interval schedules * Changed interval separator to comma, and added docs
1 parent 03aea55 commit f16819c

File tree

5 files changed

+203
-19
lines changed

5 files changed

+203
-19
lines changed

commands/deploy.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ func defineDeployCommand() *cobra.Command {
2727
}
2828

2929
deployCmd.Flags().StringVar(&releaseOffset, "releaseOffset", "1m", "Duration to wait before the first release ('5m', '1h25m', '30s')")
30-
deployCmd.Flags().StringVar(&releaseInterval, "releaseInterval", "25m", "Duration to wait between releases. ('5m', '1h25m', '30s')")
30+
deployCmd.Flags().StringVar(&releaseInterval, "releaseInterval", "25m", "Duration to wait between releases. ('5m', '1h25m', '30s')\n"+
31+
"You can do a non-linear interval by supplying more values: ('15m,10m,5m,5m,5m')")
3132
deployCmd.Flags().BoolVar(&allowForcePush, "force", false, "Allow force push if deploy branch has diverged from base")
3233
deployCmd.Flags().StringVar(&branchesString, "branches", "", "Comma separated list of branches")
3334
deployCmd.Flags().BoolVar(&skipConfirm, "skip-confirmation", false, "Create the draft without asking for user confirmation.")

mock/cli.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,21 @@ type CLI struct {
1212

1313
mu sync.Mutex
1414
ConfirmationCalls int
15+
PrintTableCalls []PrintTableVal
16+
}
17+
18+
// PrintTableVal represents the values send to the PrintTable method.
19+
type PrintTableVal struct {
20+
Header []string
21+
Values [][]string
1522
}
1623

1724
// PrintTable is a mock implementation.
18-
func (c *CLI) PrintTable(header []string, values [][]string) {}
25+
func (c *CLI) PrintTable(header []string, values [][]string) {
26+
c.mu.Lock()
27+
defer c.mu.Unlock()
28+
c.PrintTableCalls = append(c.PrintTableCalls, PrintTableVal{Header: header, Values: values})
29+
}
1930

2031
// PrintColorizedLine is a mock implementation.
2132
func (c *CLI) PrintColorizedLine(title, content string, level ergo.MessageLevel) {}

readme.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,30 @@ ergo deploy \
7575
--branches release-pe,release-mx,release-co,release-cl,release-gr
7676
```
7777

78+
##### Deploy with custom intervals
79+
80+
If you don't want a linear release interval, for example you want more time between the first and second deployment, you can specify multiple release intervals.
81+
82+
```bash
83+
ergo deploy \
84+
--owner dbaltas \
85+
--repo ergo \
86+
--releaseInterval 15m,5m,5m,5m \
87+
--branches release-pe,release-mx,release-co,release-cl,release-gr
88+
```
89+
90+
Each release will add the next interval, and starts reading the list from the beginning in case releaseInterval list is shorter than the number of branches.
91+
92+
```bash
93+
Branch Start Time
94+
release-pe 12:59 CEST
95+
release-mx 13:14 CEST
96+
release-co 13:19 CEST
97+
release-cl 13:24 CEST
98+
release-gr 13:29 CEST
99+
Deployment? [y/N]:
100+
```
101+
78102
## Github Access
79103
To communicate with github you will need a [personal access token](https://github.com/settings/tokens) added on the configuration file as `access-token` on github
80104

release/deploy.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,17 @@ func (r *Deploy) Do(
6868
r.c.PrintLine("Deploying ", release.ReleaseURL)
6969
r.c.PrintLine("Deployment start times are estimates.")
7070

71-
intervalDuration, releaseTimer, err := r.calculateReleaseTime(releaseIntervalInput, releaseOffsetInput)
71+
intervalDurations, releaseTimer, err := r.calculateReleaseTime(releaseIntervalInput, releaseOffsetInput)
7272
if err != nil {
7373
return err
7474
}
7575

7676
releaseTime := *releaseTimer
7777

78-
r.printReleaseTimeBoard(releaseTime, r.releaseBranches, intervalDuration)
78+
r.printReleaseTimeBoard(releaseTime, r.releaseBranches, intervalDurations)
7979

8080
if skipConfirm {
81-
return r.deployToAllReleaseBranches(ctx, intervalDuration, releaseTime, release, allowForcePush)
81+
return r.deployToAllReleaseBranches(ctx, intervalDurations, releaseTime, release, allowForcePush)
8282
}
8383

8484
confirm, err := r.c.Confirmation("Deployment", "No deployment", "")
@@ -97,17 +97,18 @@ func (r *Deploy) Do(
9797
r.c.PrintLine("Deployment will start in", untilReleaseTime.String())
9898
time.Sleep(untilReleaseTime)
9999

100-
return r.deployToAllReleaseBranches(ctx, intervalDuration, releaseTime, release, allowForcePush)
100+
return r.deployToAllReleaseBranches(ctx, intervalDurations, releaseTime, release, allowForcePush)
101101
}
102102

103103
func (r *Deploy) deployToAllReleaseBranches(
104104
ctx context.Context,
105-
intervalDuration time.Duration,
105+
intervalDurations []time.Duration,
106106
releaseTime time.Time,
107107
release *ergo.Release,
108108
allowForcePush bool,
109109
) error {
110110
for i, branch := range r.releaseBranches {
111+
intervalDuration := intervalDurations[i%len(intervalDurations)]
111112
if i != 0 {
112113
time.Sleep(intervalDuration)
113114
releaseTime = releaseTime.Add(intervalDuration)
@@ -128,31 +129,41 @@ func (r *Deploy) deployToAllReleaseBranches(
128129
}
129130

130131
// calculateReleaseTime calculate from string the interval between the releases.
131-
func (r *Deploy) calculateReleaseTime(releaseInterval, releaseOffset string) (time.Duration, *time.Time, error) {
132-
intervalDuration, err := time.ParseDuration(releaseInterval)
133-
if err != nil {
134-
return 0, nil, fmt.Errorf("error parsing interval: %w", err)
132+
func (r *Deploy) calculateReleaseTime(releaseInterval, releaseOffset string) ([]time.Duration, *time.Time, error) {
133+
intervalStrings := strings.Split(releaseInterval, ",")
134+
intervalDurations := make([]time.Duration, 0, len(intervalStrings))
135+
for _, interval := range intervalStrings {
136+
intervalDuration, err := time.ParseDuration(interval)
137+
if err != nil {
138+
return []time.Duration{}, nil, fmt.Errorf("error parsing interval: %w", err)
139+
}
140+
intervalDurations = append(intervalDurations, intervalDuration)
141+
135142
}
136143
offsetDuration, err := time.ParseDuration(releaseOffset)
137144
if err != nil {
138-
return 0, nil, fmt.Errorf("error parsing duration: %w", err)
145+
return []time.Duration{}, nil, fmt.Errorf("error parsing duration: %w", err)
146+
}
147+
if len(intervalDurations) == 0 {
148+
return []time.Duration{}, nil, fmt.Errorf("missing required interval durations")
139149
}
140150
releaseTime := time.Now().Add(offsetDuration)
141-
return intervalDuration, &releaseTime, nil
151+
return intervalDurations, &releaseTime, nil
142152
}
143153

144154
// printReleaseTimeBoard print the release time board.
145-
func (r *Deploy) printReleaseTimeBoard(releaseTime time.Time, releaseBranches []string, intervalDuration time.Duration) {
155+
func (r *Deploy) printReleaseTimeBoard(releaseTime time.Time, releaseBranches []string, intervalDurations []time.Duration) {
146156
var times [][]string
147157

148-
for _, branch := range releaseBranches {
158+
for i, branch := range releaseBranches {
159+
interval := intervalDurations[i%len(intervalDurations)]
149160
timesRow := []string{branch, releaseTime.Format("15:04 MST")}
150-
releaseTime = releaseTime.Add(intervalDuration)
161+
releaseTime = releaseTime.Add(interval)
151162
times = append(times, timesRow)
152163
}
153164

154165
headers := []string{"Branch", "Start Time"}
155-
cli.NewCLI().PrintTable(headers, times)
166+
r.c.PrintTable(headers, times)
156167
}
157168

158169
// updateHostReleaseBody update the host release body.

release/deploy_test.go

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"reflect"
78
"testing"
8-
9-
"github.com/beatlabs/ergo/mock"
9+
"time"
1010

1111
"github.com/beatlabs/ergo"
1212
"github.com/beatlabs/ergo/cli"
13+
"github.com/beatlabs/ergo/mock"
1314
)
1415

1516
func TestNewDeployShouldNotReturnNilObject(t *testing.T) {
@@ -299,3 +300,139 @@ func TestDoWithPublishDraftEnabledError(t *testing.T) {
299300
})
300301
}
301302
}
303+
304+
func TestNonLinearIntervals(t *testing.T) {
305+
tests := []struct {
306+
name string
307+
branches []string
308+
intervals string
309+
}{
310+
{
311+
name: "single branch, single interval",
312+
branches: []string{"b1"},
313+
intervals: "1ms",
314+
},
315+
{
316+
name: "multiple branches, single interval",
317+
branches: []string{"b1", "b2", "b3", "b4"},
318+
intervals: "1ms",
319+
},
320+
{
321+
name: "multiple branches, nonlinear intervals",
322+
branches: []string{"b1", "b2", "b3", "b4"},
323+
intervals: "10ms,5ms,1ms,1ms",
324+
},
325+
{
326+
name: "multiple branches, fewer intervals",
327+
branches: []string{"b1", "b2", "b3", "b4"},
328+
intervals: "10ms,5ms",
329+
},
330+
}
331+
332+
for _, test := range tests {
333+
t.Run(test.name, func(t *testing.T) {
334+
host := &mock.RepositoryClient{}
335+
c := &mock.CLI{}
336+
337+
host.LastReleaseFn = func() (*ergo.Release, error) {
338+
return &ergo.Release{TagName: "1.0.0"}, nil
339+
}
340+
341+
err := NewDeploy(
342+
c,
343+
host,
344+
"baseBranch",
345+
"",
346+
"",
347+
test.branches, map[string]string{},
348+
).Do(ctx, test.intervals, "1ms", false, false, false)
349+
350+
if err != nil {
351+
t.Errorf("NewDeploy().Do() returned error: %v", err)
352+
}
353+
})
354+
}
355+
}
356+
357+
func TestNonLinearIntervalHandling(t *testing.T) {
358+
ts := func(t time.Time) string {
359+
return t.Format("15:04 MST")
360+
}
361+
tests := []struct {
362+
name string
363+
branches []string
364+
intervals string
365+
expectedPrintRows [][]string
366+
}{
367+
{
368+
name: "single branch, single interval",
369+
branches: []string{"branch1"},
370+
intervals: "1m",
371+
expectedPrintRows: [][]string{
372+
{"branch1", ts(time.Now())},
373+
},
374+
},
375+
{
376+
name: "multiple branches, single interval",
377+
branches: []string{"branch1", "branch2", "branch3", "branch4"},
378+
intervals: "1m",
379+
expectedPrintRows: [][]string{
380+
{"branch1", ts(time.Now())},
381+
{"branch2", ts(time.Now().Add(1 * time.Minute))},
382+
{"branch3", ts(time.Now().Add(2 * time.Minute))},
383+
{"branch4", ts(time.Now().Add(3 * time.Minute))},
384+
},
385+
},
386+
{
387+
name: "multiple branches, nonlinear intervals",
388+
branches: []string{"branch1", "branch2", "branch3", "branch4"},
389+
intervals: "10m,5m,1m",
390+
expectedPrintRows: [][]string{
391+
{"branch1", ts(time.Now())},
392+
{"branch2", ts(time.Now().Add(10 * time.Minute))},
393+
{"branch3", ts(time.Now().Add(15 * time.Minute))},
394+
{"branch4", ts(time.Now().Add(16 * time.Minute))},
395+
},
396+
},
397+
{
398+
name: "multiple branches, fewer intervals",
399+
branches: []string{"branch1", "branch2", "branch3", "branch4"},
400+
intervals: "10m,5m",
401+
expectedPrintRows: [][]string{
402+
{"branch1", ts(time.Now())},
403+
{"branch2", ts(time.Now().Add(10 * time.Minute))},
404+
{"branch3", ts(time.Now().Add(15 * time.Minute))},
405+
{"branch4", ts(time.Now().Add(25 * time.Minute))},
406+
},
407+
},
408+
}
409+
410+
for _, test := range tests {
411+
t.Run(test.name, func(t *testing.T) {
412+
cliMock := &mock.CLI{}
413+
deploy := &Deploy{
414+
c: cliMock,
415+
releaseBranches: test.branches,
416+
}
417+
intervalDurations, releaseTimer, err := deploy.calculateReleaseTime(test.intervals, "1ms")
418+
if err != nil {
419+
t.Errorf("NewDeploy().Do() returned error: %v", err)
420+
}
421+
422+
releaseTime := *releaseTimer
423+
424+
deploy.printReleaseTimeBoard(releaseTime, deploy.releaseBranches, intervalDurations)
425+
if len(cliMock.PrintTableCalls) != 1 {
426+
t.Errorf("expected exactly one interaction with PrintTable")
427+
}
428+
PrintTableCall := cliMock.PrintTableCalls[0]
429+
expected := []string{"Branch", "Start Time"}
430+
if !reflect.DeepEqual(expected, PrintTableCall.Header) {
431+
t.Errorf("expected %v to equal %v", expected, PrintTableCall.Header)
432+
}
433+
if !reflect.DeepEqual(test.expectedPrintRows, PrintTableCall.Values) {
434+
t.Errorf("expected %v to equal %v", test.expectedPrintRows, PrintTableCall.Values)
435+
}
436+
})
437+
}
438+
}

0 commit comments

Comments
 (0)