Skip to content

Commit

Permalink
Support osx OS and linux64 combo OS/architecture in Github assets…
Browse files Browse the repository at this point in the history
…, match a Github tag to a partial version by removing extraneous text from the beginning of the tag, fix listing versions of a managed tool when versions contain extraneous text (TagName-vx.y.z), use Github API to match a pre-release instead of string-matching

Support of the `linux64` combo operating system and architecture, plus removing extraneous text from Github tags while matching a partial version, is motivated by [stedolan/jq releases](https://github.com/stedolan/jq/releases)which use tags like `jq-1.6`.
  • Loading branch information
ivanfetch committed Nov 18, 2022
1 parent 2d5b629 commit 1e8e1e0
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 17 deletions.
51 changes: 38 additions & 13 deletions github.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ type GithubAsset struct {
URL string `json:"url"`
}

var semVerRE *regexp.Regexp = regexp.MustCompile(`(.+?)[-_]v?\d+.*`)
var versionRE *regexp.Regexp = regexp.MustCompile(`(.+?)[-_]?v?\d+.*`)

// NameWithoutVersionAndComponents returns the asset name minus its version
// and any specified components. A component is matched with a preseeding
Expand All @@ -90,20 +90,25 @@ func (g GithubAsset) NameWithoutVersionAndComponents(components ...string) strin
strippedName = strings.Replace(strippedName, fmt.Sprintf("-%s", component), "", -1)
strippedName = strings.Replace(strippedName, fmt.Sprintf("_%s", component), "", -1)
}
withoutVersionMatches := semVerRE.FindStringSubmatch(strippedName)
// Attempt to strip what looks like a version, which may still be included in
// the name.
withoutVersionMatches := versionRE.FindStringSubmatch(strippedName)
if withoutVersionMatches != nil || len(withoutVersionMatches) >= 2 {
debugLog.Printf("the stripped name is %q", withoutVersionMatches[1])
debugLog.Printf("the stripped name after matching a version number is %q", withoutVersionMatches[1])
return withoutVersionMatches[1]
}
debugLog.Printf("unable to strip a version, the stripped name is %q", strippedName)
debugLog.Printf("the stripped name is %q", strippedName)
return strippedName
}

type GithubReleases []struct {
ReleaseName string `json:"name"`
TagName string `json:"tag_name"`
PreRelease bool `json:"prerelease"`
}

// tagForReleaseName returns the tag for the specified release name. The
// release name and its tag are often identical, but not always...
func (g GithubReleases) tagForReleaseName(wantName string) (tag string, found bool) {
debugLog.Printf("Looking for name %q in %d releases\n", wantName, len(g))
for _, r := range g {
Expand All @@ -116,27 +121,43 @@ func (g GithubReleases) tagForReleaseName(wantName string) (tag string, found bo
return "", false
}

// MatchTagFromPartialVersion returns a latest tag matching an imcomplete
// version E.G. return the latest tag x.y.z for a specified x.y, or x.
func (g GithubReleases) MatchTagFromPartialVersion(pv string) (tag string, found bool) {
debugLog.Printf("matching tag from partial version %q\n", pv)
tags := make([]string, len(g))
for i, j := range g {
tags[i] = j.TagName
if !j.PreRelease {
tags[i] = j.TagName
}
}
sort.Strings(tags)
LCPV := strings.ToLower(pv)
// Iterate the Github release tags backwards.
for i := len(tags) - 1; i >= 0; i-- {
LCPV := strings.ToLower(pv)
LCThisTag := strings.ToLower(tags[i])
_, foundPreRelease := stringContainsOneOfLowerCase(tags[i], "-rc", "-alpha", "-beta")
if foundPreRelease {
debugLog.Printf("skipping pre-release tag %q\n", tags[i])
continue
}
if strings.HasPrefix(LCThisTag, LCPV) || strings.HasPrefix(LCThisTag, "v"+LCPV) {
debugLog.Printf("matched tag %q for partial version %s\n", tags[i], pv)
return tags[i], true
}
}
// Try matching with extraneous text removed from the beginning of the tag,
// like tags that include the repo or release name.
var stripPrefixRE *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z-_]+(v?\d+\..*)`)
for i := len(tags) - 1; i >= 0; i-- {
LCThisTag := strings.ToLower(tags[i])
strippedMatches := stripPrefixRE.FindStringSubmatch(LCThisTag)
if strippedMatches == nil || len(strippedMatches) < 2 {
debugLog.Printf("cannot strip extraneous text from tag %q\n", LCThisTag)
continue
}
strippedTag := strippedMatches[1]
debugLog.Printf("the stripped tag is %q", strippedTag)
if strings.HasPrefix(strippedTag, LCPV) || strings.HasPrefix(strippedTag, "v"+LCPV) {
debugLog.Printf("matched tag %q after stripping prefix %q, for partial version %s\n", tags[i], strippedTag, pv)
return tags[i], true
}
}
debugLog.Printf("no partial match for %s\n", pv)
return "", false
}
Expand Down Expand Up @@ -397,11 +418,11 @@ func (g GithubRepo) DownloadReleaseForTagOSAndArch(tag, OS, arch string) (filePa
if err != nil {
return "", "", err
}
return filePath, asset.NameWithoutVersionAndComponents(matchedOS, matchedArch), nil
return filePath, asset.NameWithoutVersionAndComponents(matchedOS, matchedArch, tag), nil
}

func (g GithubRepo) DownloadReleaseForTag(tag string) (binaryPath, assetBaseName string, err error) {
debugLog.Printf("downloading Github release %q for tag %q\n", tag, g.ownerAndRepo)
debugLog.Printf("downloading Github release %q for tag %q\n", g.ownerAndRepo, tag)
downloadedFile, assetBaseName, err := g.DownloadReleaseForTagOSAndArch(tag, runtime.GOOS, runtime.GOARCH)
if err != nil {
return "", "", err
Expand All @@ -417,6 +438,10 @@ func MatchAssetByOsAndArch(assets []GithubAsset, OS, arch string) (matchedAsset
debugLog.Printf("matched this asset for OS %q and arch %q: %#v", OS, arch, asset)
return asset, matchedOS, matchedArch, true
}
if strings.EqualFold(OS, "linux") && strings.EqualFold(arch, "amd64") && strings.Contains(strings.ToLower(asset.Name), "linux64") {
debugLog.Printf("matched this asset against the combo-string linux64: %#v\n", asset)
return asset, "linux64", "linux64", true // OS and arch are linux64 to facilitate stripping components from the asset name
}
}
if strings.EqualFold(OS, "darwin") && strings.EqualFold(arch, "arm64") {
// If no Darwin/ARM64 asset is available, try AMD64 which can run under Mac OS
Expand Down
13 changes: 12 additions & 1 deletion github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func TestGithubMatchTagFromPartialVersion(t *testing.T) {
{
ReleaseName: "1.0.3-rc1",
TagName: "1.0.3-rc1",
PreRelease: true,
},
{
ReleaseName: "2.0.1", // skipped 2.0.0
Expand All @@ -49,6 +50,10 @@ func TestGithubMatchTagFromPartialVersion(t *testing.T) {
ReleaseName: "3.0.3",
TagName: "3.0.3",
},
{
ReleaseName: "jq 1.6",
TagName: "jq-1.6",
},
}

testCases := []struct {
Expand All @@ -75,13 +80,19 @@ func TestGithubMatchTagFromPartialVersion(t *testing.T) {
wantTag: "2.0.1",
expectMatch: true,
},
{
description: "match tag (with extraneous text) jq-1.6 from partial version 1.6",
version: "1.6",
wantTag: "jq-1.6",
expectMatch: true,
},
}

for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
gotTag, gotMatch := fakeGithubReleases.MatchTagFromPartialVersion(tc.version)
if tc.expectMatch && !gotMatch {
t.Fatal("expected version to match a tag")
t.Fatal("expected version to match a tag, try running tests with the JKL_DEBUG environment variable set for more information")
}
if !tc.expectMatch && gotMatch {
t.Fatalf("unexpectedly matched tag %q to version %q\n", gotTag, tc.version)
Expand Down
9 changes: 7 additions & 2 deletions toolmanaged.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,16 @@ func (t managedTool) listInstalledVersions() (versions []string, found bool, err
}
sortedVersions := make([]*hashicorpversion.Version, len(versions))
for i, v := range versions {
hv, _ := hashicorpversion.NewVersion(v)
hv, err := hashicorpversion.NewVersion(v)
if err != nil {
debugLog.Printf("using string-sort while listing installed versions - the version %q can't be converted to a version, probably because it starts with extraneous text", v)
sort.Strings(versions)
return versions, true, nil
}
sortedVersions[i] = hv
}
sort.Sort(hashicorpversion.Collection(sortedVersions))
for i, v := range sortedVersions { // reorder the original version strings by Hashicorp-sorted order.
for i, v := range sortedVersions { // reorder the original version strings by hashicorpversion.Version order
versions[i] = v.Original()
}
return versions, true, nil
Expand Down
2 changes: 1 addition & 1 deletion util.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func getAliasesForArchitecture(arch string) []string {

func getAliasesForOperatingSystem(OS string) []string {
OSAliases := map[string][]string{
"darwin": {"macos"},
"darwin": {"macos", "osx"},
}
return OSAliases[strings.ToLower(OS)]
}

0 comments on commit 1e8e1e0

Please sign in to comment.