Skip to content

Commit

Permalink
fix(query): sort github tag_names when semver (#11)
Browse files Browse the repository at this point in the history
Fixes cases where a patch for an older major/minor is released after the current latest major/minor. We now sort all the tag_names and pick the latest when semver
  • Loading branch information
JosephKav committed Apr 23, 2022
1 parent 542c226 commit c350c90
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 107 deletions.
2 changes: 1 addition & 1 deletion config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (d *Defaults) SetDefaults() {
d.Service.AutoApprove = &serviceAutoApprove
serviceAllowInvalidCerts := false
d.Service.AllowInvalidCerts = &serviceAllowInvalidCerts
serviceIgnoreMisses := false
serviceIgnoreMisses := true
d.Service.IgnoreMisses = &serviceIgnoreMisses
serviceInterval := "10m"
d.Service.Interval = &serviceInterval
Expand Down
164 changes: 104 additions & 60 deletions service/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package service

import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
Expand All @@ -24,7 +25,6 @@ import (

"github.com/coreos/go-semver/semver"
"github.com/hymenaios-io/Hymenaios/utils"
"gopkg.in/yaml.v3"
)

// Query queries the Service source, updating Service.LatestVersion
Expand Down Expand Up @@ -69,64 +69,18 @@ func (s *Service) Query() (bool, error) {
jLog.Error(err, logFrom, true)
return false, err
}
// Convert the body to string.
body := string(rawBody)
version := body

var versions []string
var releases []GitHubRelease
var filteredReleases []GitHubRelease
// GitHub service.
if *s.Type == "github" {
// Check for rate limit.
if len(body) < 500 {
if strings.Contains(body, "rate limit") {
err = errors.New("rate limit reached for GitHub")
jLog.Warn(err, logFrom, true)
return false, err
}
if !strings.Contains(body, `"tag_name"`) {
err = errors.New("github access token is invalid")
jLog.Fatal(err, logFrom, strings.Contains(body, "Bad credentials"))

err = fmt.Errorf("tag_name not found at %s\n%s", *s.URL, body)
jLog.Error(err, logFrom, true)
return false, err
}
}

err = yaml.Unmarshal(rawBody, &releases)
if err != nil {
jLog.Error(err, logFrom, true)
msg := fmt.Sprintf("Unmarshal of GitHub API data failed\n%s", err)
jLog.Error(msg, logFrom, true)
}

for i := range releases {
// If it isn't a prerelease, or it is and they're wanted
if !releases[i].PreRelease || (releases[i].PreRelease && s.GetUsePreRelease()) {
versions = append(versions, releases[i].TagName)
filteredReleases = append(filteredReleases, releases[i])
}
}

// Web service
} else {
versions = append(versions, version)
filteredReleases, err := s.GetVersions(rawBody, logFrom)
if err != nil {
return false, err
}

for i := range versions {
// Iterate through the URLCommands to filter out the version.
version, err = s.URLCommands.run(versions[i], logFrom)
// If URLCommands failed
if err != nil {
// If this is the last version, return
if i == len(versions)-1 {
// Don't log here as the `run` will already have logged
return false, err
}
// Try another version
continue
var version string
wantSemanticVersioning := s.GetSemanticVersioning()
for i := range filteredReleases {
version = filteredReleases[i].TagName
if wantSemanticVersioning && *s.Type != "url" {
version = filteredReleases[i].SemanticVersion.String()
}

// Break if version passed the regex check
Expand All @@ -143,7 +97,7 @@ func (s *Service) Query() (bool, error) {
filteredReleases[i].Assets,
logFrom,
); err != nil {
if i == len(versions)-1 {
if i == len(filteredReleases)-1 {
return false, err
}
continue
Expand All @@ -153,7 +107,7 @@ func (s *Service) Query() (bool, error) {
} else {
if err := s.regexCheckContent(
version,
body,
string(rawBody),
logFrom,
); err != nil {
return false, err
Expand All @@ -170,7 +124,6 @@ func (s *Service) Query() (bool, error) {
s.Status.SetLastQueried()
// If this version is different (new).
if version != utils.DefaultIfNil(s.Status.LatestVersion) {
wantSemanticVersioning := s.GetSemanticVersioning()
// Check for a progressive change in version.
if wantSemanticVersioning && s.Status.LatestVersion != nil {
oldVersion, err := semver.NewVersion(*s.Status.LatestVersion)
Expand All @@ -192,7 +145,7 @@ func (s *Service) Query() (bool, error) {
// return false (don't notify anything. Stay on oldVersion)
if newVersion.LessThan(*oldVersion) {
err := fmt.Errorf("queried version %q is less than the current version %q", version, *s.Status.LatestVersion)
jLog.Error(err, logFrom, true)
jLog.Warn(err, logFrom, true)
return false, err
}
}
Expand Down Expand Up @@ -241,3 +194,94 @@ func (s *Service) Query() (bool, error) {
// No version change.
return false, nil
}

func (s *Service) GetVersions(rawBody []byte, logFrom utils.LogFrom) (filteredReleases []GitHubRelease, err error) {
var releases []GitHubRelease
body := string(rawBody)
// GitHub service.
if *s.Type == "github" {
// Check for rate limit.
if len(body) < 500 {
if strings.Contains(body, "rate limit") {
err = errors.New("rate limit reached for GitHub")
jLog.Warn(err, logFrom, true)
return
}
if !strings.Contains(body, `"tag_name"`) {
err = errors.New("github access token is invalid")
jLog.Fatal(err, logFrom, strings.Contains(body, "Bad credentials"))

err = fmt.Errorf("tag_name not found at %s\n%s", *s.URL, body)
jLog.Error(err, logFrom, true)
return
}
}

if err = json.Unmarshal(rawBody, &releases); err != nil {
jLog.Error(err, logFrom, true)
msg := fmt.Errorf("unmarshal of GitHub API data failed\n%s", err)
jLog.Error(msg, logFrom, true)
}

semanticVerioning := s.GetSemanticVersioning()
for i := range releases {
// If it isn't a prerelease, or it is and they're wanted
if !releases[i].PreRelease || (releases[i].PreRelease && s.GetUsePreRelease()) {
// Check that TagName matches URLCommands
if releases[i].TagName, err = s.URLCommands.run(releases[i].TagName, logFrom); err != nil {
continue
}

// If SemVer isn't wanted, add all
if !semanticVerioning {
filteredReleases = append(filteredReleases, releases[i])
continue
}

// Else, sort the versions
semVer, err := semver.NewVersion(releases[i].TagName)
if err != nil {
continue
}
releases[i].SemanticVersion = semVer
if len(filteredReleases) == 0 {
filteredReleases = append(filteredReleases, releases[i])
continue
}
// Insertion Sort
index := len(filteredReleases)
for index != 0 {
index--
// semVer @ current is less than @ index
if releases[i].SemanticVersion.LessThan(*filteredReleases[index].SemanticVersion) {
if index == len(filteredReleases)-1 {
filteredReleases = append(filteredReleases, releases[i])
break
}
filteredReleases = append(filteredReleases[:index+1], filteredReleases[index:]...)
filteredReleases[index+1] = releases[i]
break
} else if index == 0 {
// releases[i] is newer than all filteredReleases. Prepend
filteredReleases = append([]GitHubRelease{releases[i]}, filteredReleases...)
}
}
}
}

// url service
} else {
version, err := s.URLCommands.run(body, logFrom)
if err != nil {
return filteredReleases, err
}
filteredReleases = append(filteredReleases, GitHubRelease{TagName: version})
}

if len(filteredReleases) == 0 {
err = fmt.Errorf("no releases were found matching the url_commands")
jLog.Warn(err, logFrom, true)
return
}
return filteredReleases, nil
}
95 changes: 49 additions & 46 deletions service/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package service

import (
"github.com/coreos/go-semver/semver"

"github.com/hymenaios-io/Hymenaios/notifiers/gotify"
"github.com/hymenaios-io/Hymenaios/notifiers/slack"
"github.com/hymenaios-io/Hymenaios/webhook"
Expand Down Expand Up @@ -53,58 +55,59 @@ type Service struct {

// GitHubRelease is the format of a Release on api.github.com/repos/OWNER/REPO/releases.
type GitHubRelease struct {
URL string `yaml:"url"`
AssetsURL string `yaml:"assets_url"`
UploadURL string `yaml:"upload_url"`
HTMLURL string `yaml:"html_url"`
ID uint `yaml:"id"`
Author GitHubAuthor `yaml:"author"`
NodeID string `yaml:"node_id"`
TagName string `yaml:"tag_name"`
TargetCommitish string `yaml:"target_commitish"`
Name string `yaml:"name"`
Draft bool `yaml:"draft"`
PreRelease bool `yaml:"prerelease"`
CreatedAt string `yaml:"created_at"`
PublishedAt string `yaml:"published_at"`
Assets []GitHubAsset `yaml:"assets"`
URL string `json:"url"`
AssetsURL string `json:"assets_url"`
UploadURL string `json:"upload_url"`
HTMLURL string `json:"html_url"`
ID uint `json:"id"`
Author GitHubAuthor `json:"author"`
NodeID string `json:"node_id"`
SemanticVersion *semver.Version `json:"-"`
TagName string `json:"tag_name"`
TargetCommitish string `json:"target_commitish"`
Name string `json:"name"`
Draft bool `json:"draft"`
PreRelease bool `json:"prerelease"`
CreatedAt string `json:"created_at"`
PublishedAt string `json:"published_at"`
Assets []GitHubAsset `json:"assets"`
}

// GitHubAuthor is the format of an Author on api.github.com/repos/OWNER/REPO/releases.
type GitHubAuthor struct {
Login string `yaml:"login"`
ID uint `yaml:"id"`
NodeID string `yaml:"node_id"`
AvatarURL string `yaml:"avatar_url"`
GravatarID string `yaml:"gravatar_id"`
URL string `yaml:"url"`
HTMLURL string `yaml:"html_url"`
FollowersURL string `yaml:"followers_url"`
FollowingURL string `yaml:"following_url"`
GistsURL string `yaml:"gists_url"`
StarredURL string `yaml:"starred_url"`
SubscriptionsURL string `yaml:"subscriptions_url"`
OrganizationsURL string `yaml:"organizations_url"`
ReposURL string `yaml:"repos_url"`
EventsURL string `yaml:"events_url"`
ReceivedEventsURL string `yaml:"received__events_url"`
Type string `yaml:"type"`
SiteAdmin bool `yaml:"site_admin"`
Login string `json:"login"`
ID uint `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received__events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
}

// GitHubAsset is the format of an Asset on api.github.com/repos/OWNER/REPO/releases.
type GitHubAsset struct {
URL string `yaml:"url"`
ID uint `yaml:"id"`
NodeID string `yaml:"node_id"`
Name string `yaml:"name"`
Label string `yaml:"label"`
Uploader GitHubAuthor `yaml:"uploader"`
ContentType string `yaml:"content_type"`
State string `yaml:"state"`
Size uint `yaml:"size"`
DownloadCount uint `yaml:"download_count"`
CreatedAt string `yaml:"created_at"`
UpdatedAt string `yaml:"updated_at"`
BrowserDownloadURL string `yaml:"browser_download_url"`
URL string `json:"url"`
ID uint `json:"id"`
NodeID string `json:"node_id"`
Name string `json:"name"`
Label string `json:"label"`
Uploader GitHubAuthor `json:"uploader"`
ContentType string `json:"content_type"`
State string `json:"state"`
Size uint `json:"size"`
DownloadCount uint `json:"download_count"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
BrowserDownloadURL string `json:"browser_download_url"`
}

0 comments on commit c350c90

Please sign in to comment.