forked from sourcegraph/sourcegraph-public-snapshot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathversion.go
More file actions
129 lines (106 loc) · 3.37 KB
/
version.go
File metadata and controls
129 lines (106 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package srccli
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sort"
"github.com/Masterminds/semver"
"github.com/pkg/errors"
"github.com/sourcegraph/sourcegraph/internal/linkheader"
)
type releaseMeta struct {
TagName string `json:"tag_name"`
Draft bool `json:"draft"`
Prerelease bool `json:"prerelease"`
}
const githubAPIReleasesEndpoint = "https://api.github.com/repos/sourcegraph/src-cli/releases"
// Version returns the highest public version currently available via the GitHub release page
// that has the same major and minor versions as the configured minimum version. This allows
// us to recommend patch updates without having to release a sourcegraph instance with a bumped
// constant.
func Version() (string, error) {
minimumVersion, err := semver.NewVersion(MinimumVersion)
if err != nil {
return "", errors.Wrap(err, "non-semantic minimum src-cli version")
}
versions, err := releaseVersions(githubAPIReleasesEndpoint)
if err != nil {
return "", errors.Wrap(err, "fetching src-cli release versions")
}
recommendedVersion, err := highestMatchingVersion(minimumVersion, versions)
if err != nil {
return "", errors.Wrap(err, "comparing versions")
}
return recommendedVersion.String(), nil
}
// highestMatchingVersion returns the highest version with the same major and
// minor value as the given minimum version.
func highestMatchingVersion(minimumVersion *semver.Version, versions []*semver.Version) (*semver.Version, error) {
constraint, err := semver.NewConstraint(fmt.Sprintf("~%d.%d.x", minimumVersion.Major(), minimumVersion.Minor()))
if err != nil {
return nil, errors.Wrap(err, "invalid range")
}
var matching semver.Collection
for _, version := range versions {
if constraint.Check(version) {
matching = append(matching, version)
}
}
if len(matching) == 0 {
return minimumVersion, nil
}
sort.Sort(matching)
return matching[len(matching)-1], nil
}
// releaseVersions requests the given URL and all subsequent pages of
// releases. Returns the non-draft, non-prerelease items with a valid
// semver tag.
func releaseVersions(url string) ([]*semver.Version, error) {
versions, nextURL, err := releaseVersionsPage(url)
if err != nil {
return nil, err
}
if nextURL != "" {
tail, err := releaseVersions(nextURL)
if err != nil {
return nil, err
}
versions = append(versions, tail...)
}
return versions, nil
}
// releaseVersionsPage requests the given URL and returns the non-draft,
// non-prerelease items with a valid semver tag and the url for the next page
// of results (if one exists).
func releaseVersionsPage(url string) ([]*semver.Version, string, error) {
resp, err := http.DefaultClient.Get(url)
if err != nil {
return nil, "", err
}
defer resp.Body.Close()
respContent, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, "", err
}
if resp.StatusCode >= 400 {
return nil, "", errors.Errorf("Invalid response from GitHub: %s", respContent)
}
var releases []releaseMeta
if err := json.Unmarshal(respContent, &releases); err != nil {
return nil, "", err
}
versions := []*semver.Version{}
for _, release := range releases {
if release.Draft || release.Prerelease {
continue
}
version, err := semver.NewVersion(release.TagName)
if err != nil {
continue
}
versions = append(versions, version)
}
nextURL, _ := linkheader.ExtractNextURL(resp)
return versions, nextURL, nil
}