Skip to content
This repository has been archived by the owner on Aug 6, 2021. It is now read-only.

Update scripts #3

Merged
merged 2 commits into from
Oct 8, 2020
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
62 changes: 48 additions & 14 deletions updater/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package updater
import (
"fmt"
"regexp"
"strconv"
"strings"
"time"

"github.com/dependabot/gomodules-extracted/cmd/go/_internal_/semver"
)
Expand All @@ -17,30 +19,23 @@ type Group struct {

// Parameters that apply to members:
// Range is a comma separated list of allowed semver ranges
Range string `yaml:"range"`
Frequency Frequency `yaml:"frequency"`
Range string `yaml:"range"`
CoolDown string `yaml:"cooldown"`
PreScript string `yaml:"pre-script"`
PostScript string `yaml:"post-script"`

compiledPattern *regexp.Regexp
}

type Frequency string

const (
FrequencyDaily Frequency = "daily"
FrequencyWeekly Frequency = "weekly"
)

func (g *Group) Validate() error {
if g.Name == "" {
return fmt.Errorf("groups must specify name")
}
if g.Pattern == "" {
return fmt.Errorf("groups must specify pattern")
}
switch g.Frequency {
case "", FrequencyDaily, FrequencyWeekly:
default:
return fmt.Errorf("frequency must be: [%s,%s]", FrequencyDaily, FrequencyWeekly)
if !durPattern.MatchString(g.CoolDown) {
return fmt.Errorf("invalid cooldown, expected ISO8601 duration: %q", g.CoolDown)
}

if strings.HasPrefix(g.Pattern, "/") && strings.HasSuffix(g.Pattern, "/") {
Expand All @@ -55,7 +50,7 @@ func (g *Group) Validate() error {
return nil
}

func (g Group) InRange(v string) bool {
func (g *Group) InRange(v string) bool {
for _, rangeCond := range strings.Split(g.Range, ",") {
rangeCond = strings.TrimSpace(rangeCond)
switch {
Expand Down Expand Up @@ -87,3 +82,42 @@ func cleanRange(rangeCond string, prefixLen int) string {
}
return s
}

var durPattern = regexp.MustCompile(`P?(((?P<year>\d+)Y)?((?P<month>\d+)M)?((?P<day>\d+)D)|(?P<week>\d+)W)?`)

const (
oneYear = 8766 * time.Hour
oneMonth = 730*time.Hour + 30*time.Minute
oneWeek = 7 * 24 * time.Hour
oneDay = 24 * time.Hour
)

func (g *Group) CoolDownDuration() time.Duration {
m := durPattern.FindStringSubmatch(g.CoolDown)

var ret time.Duration
for i, name := range durPattern.SubexpNames() {
part := m[i]
if i == 0 || name == "" || part == "" {
continue
}

val, err := strconv.Atoi(part)
if err != nil {
return 0
}
valDur := time.Duration(val)
switch name {
case "year":
ret += valDur * oneYear
case "month":
ret += valDur * oneMonth
case "week":
ret += valDur * oneWeek
case "day":
ret += valDur * oneDay
}
}

return ret
}
26 changes: 24 additions & 2 deletions updater/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,32 @@ package updater_test
import (
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thepwagner/action-update/updater"
)

func TestGroup_CoolDownDuration(t *testing.T) {
g := updater.Group{Name: "test", Pattern: "test"}

cases := map[string]time.Duration{
"P1D": 24 * time.Hour,
"1D": 24 * time.Hour,
"1W": 7 * 24 * time.Hour,
}

for in, expected := range cases {
t.Run(in, func(t *testing.T) {
g.CoolDown = in
err := g.Validate()
require.NoError(t, err)
assert.Equal(t, expected, g.CoolDownDuration())
})
}
}

func TestGroup_InRange(t *testing.T) {
cases := map[string]struct {
included []string
Expand Down Expand Up @@ -44,14 +65,15 @@ func TestGroup_InRange(t *testing.T) {

for r, tc := range cases {
t.Run(r, func(t *testing.T) {
u := &updater.Group{Range: r}
for _, v := range tc.included {
t.Run(fmt.Sprintf("includes %s", v), func(t *testing.T) {
assert.True(t, updater.Group{Range: r}.InRange(v))
assert.True(t, u.InRange(v))
})
}
for _, v := range tc.excluded {
t.Run(fmt.Sprintf("excludes %q", v), func(t *testing.T) {
assert.False(t, updater.Group{Range: r}.InRange(v))
assert.False(t, u.InRange(v))
})
}
})
Expand Down
8 changes: 3 additions & 5 deletions updater/groups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ func TestParseGroups(t *testing.T) {
frequency: weekly
range: ">=v1.4.0, <v2"`,
expected: updater.Groups{{
Name: "foo",
Pattern: "github.com/thepwagner",
Frequency: "weekly",
Range: ">=v1.4.0, <v2",
Name: "foo",
Pattern: "github.com/thepwagner",
Range: ">=v1.4.0, <v2",
}},
},
"multiple": {
Expand Down Expand Up @@ -83,7 +82,6 @@ func TestParseGroups(t *testing.T) {
for i, g := range groups {
assert.Equal(t, tc.expected[i].Name, g.Name)
assert.Equal(t, tc.expected[i].Pattern, g.Pattern)
assert.Equal(t, tc.expected[i].Frequency, g.Frequency)
assert.Equal(t, tc.expected[i].Range, g.Range)
}
}
Expand Down
27 changes: 27 additions & 0 deletions updater/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package updater
import (
"context"
"fmt"
"os"
"os/exec"

"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -214,14 +216,39 @@ func (u *RepoUpdater) groupedUpdate(ctx context.Context, log logrus.FieldLogger,
return 0, fmt.Errorf("switching to target branch: %w", err)
}

if err := u.updateScript(ctx, "pre", group.PreScript); err != nil {
return 0, fmt.Errorf("executing pre-update script: %w", err)
}

for _, update := range updates {
if err := u.updater.ApplyUpdate(ctx, update); err != nil {
return 0, fmt.Errorf("applying batched update: %w", err)
}
}

if err := u.updateScript(ctx, "post", group.PostScript); err != nil {
return 0, fmt.Errorf("executing pre-update script: %w", err)
}

if err := u.repo.Push(ctx, updates...); err != nil {
return 0, fmt.Errorf("pushing update: %w", err)
}
return len(updates), nil
}

func (u *RepoUpdater) updateScript(ctx context.Context, label, script string) error {
if script == "" {
return nil
}
cmd := exec.CommandContext(ctx, "/bin/sh", "-c", script)
cmd.Dir = u.repo.Root()
out := os.Stdout
_, _ = fmt.Fprintf(out, "--- start %s update script ---\n", label)
cmd.Stdout = out
cmd.Stderr = out
if err := cmd.Run(); err != nil {
return err
}
_, _ = fmt.Fprintf(out, "--- end %s update script ---\n", label)
return nil
}
46 changes: 46 additions & 0 deletions updater/updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package updater_test
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/mock"
Expand Down Expand Up @@ -120,3 +122,47 @@ func TestRepoUpdater_UpdateAll_MultipleGrouped(t *testing.T) {
r.AssertExpectations(t)
u.AssertExpectations(t)
}

func TestRepoUpdater_UpdateAll_Scripts(t *testing.T) {
cases := []*updater.Group{
{
Name: groupName,
Pattern: "github.com/foo",
PreScript: `echo "sup" && touch token`,
},
{
Name: groupName,
Pattern: "github.com/foo",
PostScript: `echo "sup" && touch token`,
},
}

for _, group := range cases {
err := group.Validate()
require.NoError(t, err)

tmpDir := t.TempDir()
tokenPath := filepath.Join(tmpDir, "token")
r := &mockRepo{}
u := &mockUpdater{}
ru := updater.NewRepoUpdater(r, u, updater.WithGroups(group))
ctx := context.Background()

r.On("SetBranch", baseBranch).Return(nil)
dep := updater.Dependency{Path: mockUpdate.Path, Version: mockUpdate.Previous}
u.On("Dependencies", ctx).Return([]updater.Dependency{dep}, nil)
availableUpdate := mockUpdate // avoid pointer to shared reference
u.On("Check", ctx, dep, mock.Anything).Return(&availableUpdate, nil)
r.On("NewBranch", baseBranch, "action-update-go/main/foo").Times(1).Return(nil)
u.On("ApplyUpdate", ctx, mock.Anything).Times(1).Return(nil)
r.On("Push", ctx, mock.Anything, mock.Anything).Times(1).Return(nil)
r.On("Root").Return(tmpDir)

err = ru.UpdateAll(ctx, baseBranch)
require.NoError(t, err)
r.AssertExpectations(t)
u.AssertExpectations(t)
_, err = os.Stat(tokenPath)
require.NoError(t, err)
}
}