Skip to content

Commit

Permalink
refactor(github): Relying only on tag for getting latest version
Browse files Browse the repository at this point in the history
  • Loading branch information
ViBiOh committed May 22, 2020
1 parent 38038cb commit 02c3b85
Show file tree
Hide file tree
Showing 10 changed files with 81 additions and 215 deletions.
73 changes: 14 additions & 59 deletions pkg/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,14 @@ var (
apiURL = "https://api.github.com"
)

// Release describes a Github Release
type Release struct {
Repository string `json:"repository"`
TagName string `json:"tag_name"`
}

// Tag describes a Github Tag
type Tag struct {
Name string `json:"name"`
}

// App of package
type App interface {
LastRelease(repository string) (Release, error)
LatestVersion(repository string) (semver.Version, error)
}

// Config of package
Expand Down Expand Up @@ -60,73 +54,40 @@ func (a app) newClient() *request.Request {
return request.New().Header("Authorization", fmt.Sprintf("token %s", a.token))
}

func (a app) LastRelease(repository string) (Release, error) {
release, latestErr := a.latestRelease(repository)
if latestErr == nil {
return release, nil
}

release, tagsErr := a.parseTags(repository)
if tagsErr == nil {
return release, nil
}

return Release{}, fmt.Errorf("unable to retrieve release: %s then %s", latestErr, tagsErr)
}

func (a app) latestRelease(repository string) (Release, error) {
var release Release

req := a.newClient()
resp, err := req.Get(fmt.Sprintf("%s/repos/%s/releases/latest", apiURL, repository)).Send(context.Background(), nil)
func (a app) LatestVersion(repository string) (semver.Version, error) {
version, err := a.parseTags(repository)
if err != nil {
return release, fmt.Errorf("unable to get latest release for %s: %s", repository, err)
return version, fmt.Errorf("unable to retrieve version: %s", err)
}

payload, err := request.ReadBodyResponse(resp)
if err != nil {
return release, fmt.Errorf("unable to read release body for %s: %s", repository, err)
}

if err := json.Unmarshal(payload, &release); err != nil {
return release, fmt.Errorf("unable to parse release body for %s: %s", repository, err)
}

release.Repository = repository

return release, err
return version, nil
}

func (a app) parseTags(repository string) (Release, error) {
release := Release{
Repository: repository,
}

func (a app) parseTags(repository string) (semver.Version, error) {
page := 1
version := semver.Version{}
var version semver.Version

req := a.newClient()
for {
resp, err := req.Get(fmt.Sprintf("%s/repos/%s/tags?per_page=100&page=%d", apiURL, repository, page)).Send(context.Background(), nil)
if err != nil {
return release, fmt.Errorf("unable to get tags %s: %s", repository, err)
return version, fmt.Errorf("unable to list page %d of tags: %s", page, err)
}

payload, err := request.ReadBodyResponse(resp)
if err != nil {
return release, fmt.Errorf("unable to read tags body for %s: %s", repository, err)
return version, fmt.Errorf("unable to read page %d tags body: %s", page, err)
}

var tags []Tag
if err := json.Unmarshal(payload, &tags); err != nil {
return release, fmt.Errorf("unable to parse tags body for %s: %s", repository, err)
return version, fmt.Errorf("unable to parse page %d tags body: %s", page, err)
}

for _, tag := range tags {
tagVersion, err := semver.Parse(tag.Name)
if err == nil && tagVersion.IsGreater(version) {
version = tagVersion
release.TagName = tag.Name
}
}

Expand All @@ -137,19 +98,13 @@ func (a app) parseTags(repository string) (Release, error) {
page++
}

if len(release.TagName) == 0 {
return release, fmt.Errorf("unable to find semver in tags for %s", repository)
if version == semver.NoneVersion {
return version, fmt.Errorf("unable to find semver in tags for %s", repository)
}

return release, nil
return version, nil
}

func isLastPage(resp *http.Response) bool {
for _, link := range resp.Header.Values("Link") {
if strings.Contains(link, `rel="next"`) {
return true
}
}

return false
return !strings.Contains(resp.Header.Get("Link"), `rel="next"`)
}
88 changes: 0 additions & 88 deletions pkg/github/github_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package github

import (
"errors"
"flag"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
)
Expand Down Expand Up @@ -38,87 +34,3 @@ func TestFlags(t *testing.T) {
})
}
}

func TestLastRelease(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "unknown") {
w.WriteHeader(http.StatusNotFound)
return
}

if strings.Contains(r.URL.Path, "invalid") {
w.Write([]byte("{invalid: json}"))
return
}

w.Write([]byte(`{"repository": "vibioh/ketchup", "tag_name": "1.0.0"}`))
}))

savedURL := apiURL
apiURL = server.URL

defer func() {
apiURL = savedURL
server.Close()
}()

token := "secret"

type args struct {
repository string
}

var cases = []struct {
intention string
args args
want Release
wantErr error
}{
{
"error",
args{
repository: "unknown",
},
Release{},
errors.New("unable to get latest release for unknown"),
},
{
"invalid payload",
args{
repository: "invalid",
},
Release{},
errors.New("unable to parse release body for invalid"),
},
{
"success",
args{
repository: "vibioh/ketchup",
},
Release{Repository: "vibioh/ketchup", TagName: "1.0.0"},
nil,
},
}

for _, tc := range cases {
t.Run(tc.intention, func(t *testing.T) {
got, gotErr := New(Config{token: &token}).LastRelease(tc.args.repository)

failed := false

if tc.wantErr == nil && gotErr != nil {
failed = true
} else if tc.wantErr != nil && gotErr == nil {
failed = true
} else if tc.wantErr != nil && !strings.Contains(gotErr.Error(), tc.wantErr.Error()) {
failed = true
} else if !reflect.DeepEqual(got, tc.want) {
failed = true
}

if failed {
t.Errorf("LastRelease() = (%+v, `%s`), want (%+v, `%s`)", got, gotErr, tc.want, tc.wantErr)
}
})
}
}
14 changes: 8 additions & 6 deletions pkg/model/ketchup.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package model

import "github.com/ViBiOh/ketchup/pkg/github"
import (
"github.com/ViBiOh/ketchup/pkg/semver"
)

var (
// NoneKetchup is an undefined ketchup
Expand Down Expand Up @@ -48,8 +50,8 @@ func (a KetchupByPriority) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

// Release is when new version is out
type Release struct {
Repository Repository
Release github.Release
Repository Repository `json:"repository"`
Version semver.Version `json:"version"`
}

// ReleaseByRepositoryID sort release by repository ID
Expand All @@ -59,10 +61,10 @@ func (a ReleaseByRepositoryID) Len() int { return len(a) }
func (a ReleaseByRepositoryID) Less(i, j int) bool { return a[i].Repository.ID < a[j].Repository.ID }
func (a ReleaseByRepositoryID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

// NewRelease creates a new release from its objects
func NewRelease(repository Repository, release github.Release) Release {
// NewRelease creates a new version from its objects
func NewRelease(repository Repository, version semver.Version) Release {
return Release{
Repository: repository,
Release: release,
Version: version,
}
}
10 changes: 5 additions & 5 deletions pkg/model/ketchup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"sort"
"testing"

"github.com/ViBiOh/ketchup/pkg/github"
"github.com/ViBiOh/ketchup/pkg/semver"
)

func TestKetchupByRepositoryID(t *testing.T) {
Expand Down Expand Up @@ -134,13 +134,13 @@ func TestReleaseByRepositoryID(t *testing.T) {
"simple",
args{
array: []Release{
NewRelease(Repository{ID: 10}, github.Release{}),
NewRelease(Repository{ID: 1}, github.Release{}),
NewRelease(Repository{ID: 10}, semver.Version{}),
NewRelease(Repository{ID: 1}, semver.Version{}),
},
},
[]Release{
NewRelease(Repository{ID: 1}, github.Release{}),
NewRelease(Repository{ID: 10}, github.Release{}),
NewRelease(Repository{ID: 1}, semver.Version{}),
NewRelease(Repository{ID: 10}, semver.Version{}),
},
},
}
Expand Down
24 changes: 9 additions & 15 deletions pkg/scheduler/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,29 +116,28 @@ func (a app) getNewReleases(ctx context.Context) ([]model.Release, error) {
for _, repository := range repositories {
count++

release, err := a.githubApp.LastRelease(repository.Name)
latestVersion, err := a.githubApp.LatestVersion(repository.Name)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to get latest version of %s: %s", repository.Name, err)
}

if release.TagName == repository.Version {
if latestVersion.Name == repository.Version {
continue
}

releaseVersion, releaseErr := semver.Parse(release.TagName)
repositoryVersion, repositoryErr := semver.Parse(repository.Version)
if releaseErr == nil && repositoryErr == nil && repositoryVersion.IsGreater(releaseVersion) {
if repositoryErr == nil && repositoryVersion.IsGreater(latestVersion) {
continue
}

logger.Info("New version available for %s: %s", repository.Name, release.TagName)
repository.Version = release.TagName
logger.Info("New version available for %s: %s", repository.Name, latestVersion.Name)
repository.Version = latestVersion.Name

if err := a.repositoryService.Update(ctx, repository); err != nil {
return nil, fmt.Errorf("unable to update repository %s: %s", repository.Name, err)
}

newReleases = append(newReleases, model.NewRelease(repository, release))
newReleases = append(newReleases, model.NewRelease(repository, latestVersion))
}

if uint64(page*pageSize) < totalCount {
Expand Down Expand Up @@ -176,7 +175,7 @@ func (a app) getKetchupToNotify(ctx context.Context, releases []model.Release) (
break
}

if ketchup.Version != release.Release.TagName {
if ketchup.Version != release.Version.Name {
if userToNotify[ketchup.User] != nil {
userToNotify[ketchup.User] = append(userToNotify[ketchup.User], release)
} else {
Expand Down Expand Up @@ -206,13 +205,8 @@ func (a app) sendNotification(ctx context.Context, ketchupToNotify map[model.Use
for user, releases := range ketchupToNotify {
logger.Info("Sending email to %s for %d releases", user.Email, len(releases))

githubReleases := make([]github.Release, len(releases))
for index, release := range releases {
githubReleases[index] = release.Release
}

payload := map[string]interface{}{
"releases": githubReleases,
"releases": releases,
}

email := mailer.NewEmail(a.mailerApp).Template("ketchup").From("ketchup@vibioh.fr").As("Ketchup").To(user.Email).Data(payload)
Expand Down
14 changes: 7 additions & 7 deletions pkg/scheduler/scheduler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"strings"
"testing"

"github.com/ViBiOh/ketchup/pkg/github"
"github.com/ViBiOh/ketchup/pkg/model"
"github.com/ViBiOh/ketchup/pkg/semver"
)

type testKetchupService struct{}
Expand Down Expand Up @@ -89,8 +89,8 @@ func TestFlags(t *testing.T) {
func TestGetKetchupToNotify(t *testing.T) {
firstRelease := []model.Release{
{
Release: github.Release{
TagName: "1.0.1",
Version: semver.Version{
Name: "1.0.1",
},
Repository: model.Repository{
ID: 1,
Expand All @@ -102,8 +102,8 @@ func TestGetKetchupToNotify(t *testing.T) {

secondRelease := []model.Release{
{
Release: github.Release{
TagName: "1.0.1",
Version: semver.Version{
Name: "1.0.1",
},
Repository: model.Repository{
ID: 1,
Expand All @@ -112,8 +112,8 @@ func TestGetKetchupToNotify(t *testing.T) {
},
},
{
Release: github.Release{
TagName: "1.0.1",
Version: semver.Version{
Name: "1.0.1",
},
Repository: model.Repository{
ID: 2,
Expand Down
Loading

0 comments on commit 02c3b85

Please sign in to comment.