Skip to content

Commit

Permalink
Correctly name downloaded Github assets with dashes or underscores in…
Browse files Browse the repository at this point in the history
… their names, share OS and architecture aliases among providers

Github assets with dashes or underscores are correctly named, by stripping the operating system and architecture that was originally used to match that asset. These components are stripped regardless of their order in the asset name.

Operating system and architecture aliases are now shared among providers. For example, `Darwin = Mac` or `amd64 = x86_64`.
  • Loading branch information
ivanfetch committed Aug 29, 2022
1 parent d4ee835 commit 1f6c85d
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 63 deletions.
68 changes: 33 additions & 35 deletions github.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,26 @@ type GithubAsset struct {
URL string `json:"url"`
}

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

// GetBaseName returns the asset name after attempting to strip version,
// architecture, operating system, and file extension.
func (g GithubAsset) GetBaseName() string {
matches := assetBaseNameRE.FindStringSubmatch(g.Name)
if matches == nil || len(matches) < 2 {
simplerMatches := strings.FieldsFunc(g.Name, func(r rune) bool {
return r == '-' || r == '_'
})
if len(simplerMatches) == 0 {
debugLog.Printf("unable to match a base asset name from %q, returning the full asset name", g.Name)
return g.Name
}
debugLog.Printf("matched simpler base name %q for asset name %q", simplerMatches[0], g.Name)
return simplerMatches[0]
}
debugLog.Printf("matched base name %q for asset name %q", matches[1], g.Name)
return matches[1]
// NameWithoutVersionAndComponents returns the asset name minus its version
// and any specified components. A component is matched with a preseeding
// underscore (_) or dash (-). For example, specifying a component of "darwin"
// would strip "-darwin" and "_darwin".
func (g GithubAsset) NameWithoutVersionAndComponents(components ...string) string {
debugLog.Printf("stripping the asset name %s of its version and components %v", g.Name, components)
var strippedName = g.Name
for _, component := range components {
strippedName = strings.Replace(strippedName, fmt.Sprintf("-%s", component), "", -1)
strippedName = strings.Replace(strippedName, fmt.Sprintf("_%s", component), "", -1)
}
withoutVersionMatches := semVerRE.FindStringSubmatch(strippedName)
if withoutVersionMatches != nil || len(withoutVersionMatches) >= 2 {
debugLog.Printf("the stripped name is %q", withoutVersionMatches[1])
return withoutVersionMatches[1]
}
debugLog.Printf("unable to strip a version, the stripped name is %q", strippedName)
return strippedName
}

type GithubReleases []struct {
Expand Down Expand Up @@ -106,7 +107,8 @@ func (g GithubReleases) MatchTagFromPartialVersion(pv string) (tag string, found
for i := len(tags) - 1; i >= 0; i-- {
LCPV := strings.ToLower(pv)
LCThisTag := strings.ToLower(tags[i])
if stringContainsOneOf(LCThisTag, "-rc", "-alpha", "-beta") {
_, foundPreRelease := stringContainsOneOfLowerCase(tags[i], "-rc", "-alpha", "-beta")
if foundPreRelease {
debugLog.Printf("skipping pre-release tag %q\n", tags[i])
continue
}
Expand Down Expand Up @@ -367,12 +369,15 @@ func (g GithubRepo) DownloadReleaseForTagOSAndArch(tag, OS, arch string) (filePa
if err != nil {
return "", "", err
}
asset, ok := MatchAssetByOsAndArch(assets, OS, arch)
asset, matchedOS, matchedArch, ok := MatchAssetByOsAndArch(assets, OS, arch)
if !ok {
return "", "", fmt.Errorf("no asset found matching Github owner/repository %s, tag %s, OS %s, and architecture %s", g.ownerAndRepo, tag, OS, arch)
}
filePath, err = g.Download(asset)
return filePath, asset.GetBaseName(), err
if err != nil {
return "", "", err
}
return filePath, asset.NameWithoutVersionAndComponents(matchedOS, matchedArch), nil
}

func (g GithubRepo) DownloadReleaseForTag(tag string) (binaryPath, assetBaseName string, err error) {
Expand All @@ -384,27 +389,20 @@ func (g GithubRepo) DownloadReleaseForTag(tag string) (binaryPath, assetBaseName
return downloadedFile, assetBaseName, nil
}

func MatchAssetByOsAndArch(assets []GithubAsset, OS, arch string) (GithubAsset, bool) {
archAliases := map[string][]string{
"amd64": {"x86_64"},
}
OSAliases := map[string][]string{
"darwin": {"macos"},
}
LCOS := strings.ToLower(OS)
LCArch := strings.ToLower(arch)
func MatchAssetByOsAndArch(assets []GithubAsset, OS, arch string) (matchedAsset GithubAsset, matchedOS, matchedArch string, successfulMatch bool) {
for _, asset := range assets {
LCAssetName := strings.ToLower(asset.Name)
if stringContainsOneOf(LCAssetName, LCOS, OSAliases[LCOS]...) && stringContainsOneOf(LCAssetName, LCArch, archAliases[LCArch]...) {
matchedOS, foundOS := stringContainsOneOfLowerCase(asset.Name, OS, getAliasesForOperatingSystem(OS)...)
matchedArch, foundArch := stringContainsOneOfLowerCase(asset.Name, arch, getAliasesForArchitecture(arch)...)
if foundOS && foundArch {
debugLog.Printf("matched this asset for OS %q and arch %q: %#v", OS, arch, asset)
return asset, true
return asset, matchedOS, matchedArch, true
}
}
if LCOS == "darwin" && LCArch == "arm64" {
if strings.EqualFold(OS, "darwin") && strings.EqualFold(arch, "arm64") {
// If no Darwin/ARM64 asset is available, try AMD64 which can run under Mac OS
// Rosetta.
debugLog.Println("trying to match Github asset for Darwin/AMD64 as none were found for ARM64")
return MatchAssetByOsAndArch(assets, OS, "amd64")
}
return GithubAsset{}, false
return GithubAsset{}, "", "", false
}
25 changes: 14 additions & 11 deletions github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,28 +93,31 @@ func TestGithubMatchTagFromPartialVersion(t *testing.T) {
}
}

func TestGithubAssetGetBaseName(t *testing.T) {
func TestGithubAssetNameWithoutVersionAndComponents(t *testing.T) {
t.Parallel()
testCases := []struct {
description string
asset jkl.GithubAsset
want string
description string
asset jkl.GithubAsset
removeComponents []string
want string
}{
{
description: "archived binary with version, OS, and architecture",
asset: jkl.GithubAsset{Name: "app_v1.2.3_darwin_x64.tar.gz"},
want: "app",
description: "archived binary with version, OS, and architecture",
asset: jkl.GithubAsset{Name: "app_v1.2.3_darwin_x64.tar.gz"},
removeComponents: []string{"darwin", "x64"},
want: "app",
},
{
description: "not-archived binary with no version",
asset: jkl.GithubAsset{Name: "app-darwin-amd64"},
want: "app",
description: "not-archived binary with no version",
asset: jkl.GithubAsset{Name: "app-darwin-amd64"},
removeComponents: []string{"darwin", "amd64"},
want: "app",
},
}

for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
got := tc.asset.GetBaseName()
got := tc.asset.NameWithoutVersionAndComponents(tc.removeComponents...)
if tc.want != got {
t.Errorf("want base name %q, got %q for asset %q", tc.want, got, tc.asset.Name)
}
Expand Down
9 changes: 2 additions & 7 deletions hashicorp.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,18 +331,12 @@ func (h HashicorpProduct) DownloadReleaseForVersion(version string) (binaryPath,

func MatchBuildByOsAndArch(builds []hashicorpBuild, OS, arch string) (hashicorpBuild, bool) {
debugLog.Printf("matching Hashicorp build by OS %q and architecture %q", OS, arch)
archAliases := map[string][]string{
"amd64": {"x86_64"},
}
OSAliases := map[string][]string{
"darwin": {"macos"},
}
LCOS := strings.ToLower(OS)
LCArch := strings.ToLower(arch)
for _, build := range builds {
LCBuildArch := strings.ToLower(build.Arch)
LCBuildOS := strings.ToLower(build.OS)
if stringEqualFoldOneOf(LCBuildOS, LCOS, OSAliases[LCOS]...) && stringEqualFoldOneOf(LCBuildArch, LCArch, archAliases[LCArch]...) {
if stringEqualFoldOneOf(LCBuildOS, LCOS, getAliasesForOperatingSystem(LCOS)...) && stringEqualFoldOneOf(LCBuildArch, LCArch, getAliasesForArchitecture(LCArch)...) {
debugLog.Printf("matched this asset for OS %q and arch %q: %#v", OS, arch, build)
return build, true
}
Expand All @@ -353,5 +347,6 @@ func MatchBuildByOsAndArch(builds []hashicorpBuild, OS, arch string) (hashicorpB
debugLog.Println("trying to match Hashicorp build for Darwin/AMD64 as none were found for ARM64")
return MatchBuildByOsAndArch(builds, OS, "amd64")
}
debugLog.Printf("no Hashicorp build matched OS %s and architecture %s", OS, arch)
return hashicorpBuild{}, false
}
16 changes: 12 additions & 4 deletions jkl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,23 @@ func TestMatchGithubAsset(t *testing.T) {
},
}

got, ok := jkl.MatchAssetByOsAndArch(testAssets, "darwin", "amd64")
want := jkl.GithubAsset{
gotAsset, gotOS, gotArch, ok := jkl.MatchAssetByOsAndArch(testAssets, "darwin", "amd64")
wantAsset := jkl.GithubAsset{
Name: "prme_0.0.6_Darwin_x86_64.tar.gz",
URL: "https://api.github.com/repos/ivanfetch/PRMe/releases/assets/47905345",
}
if !ok {
t.Fatal("no asset matched")
}
if !cmp.Equal(want, got) {
t.Fatalf("want vs. got: %s", cmp.Diff(want, got))
if !cmp.Equal(wantAsset, gotAsset) {
t.Fatalf("want vs. got: %s", cmp.Diff(wantAsset, gotAsset))
}
wantOS := "darwin"
if wantOS != gotOS {
t.Fatalf("want OS %s, got %s", wantOS, gotOS)
}
wantArch := "x86_64"
if wantArch != gotArch {
t.Fatalf("want architecture %s, got %s", wantArch, gotArch)
}
}
27 changes: 21 additions & 6 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import (
"strings"
)

// StringContainsOneOf returns true if one of the sub-strings is contained
// within s.
func stringContainsOneOf(s, firstSubstr string, additionalSubstrs ...string) bool {
// stringContainsOneOfLowerCase reports the first substring contained in s, returning
// true if a match is found, and the original substring that matched.
// Substrings are matched using lower-case.
func stringContainsOneOfLowerCase(s, firstSubstr string, additionalSubstrs ...string) (match string, found bool) {
for _, substr := range append([]string{firstSubstr}, additionalSubstrs...) {
if strings.Contains(s, substr) {
return true
if strings.Contains(strings.ToLower(s), strings.ToLower(substr)) {
return substr, true
}
}
return false
return "", false
}

// stringEqualFoldOneOf returns true if the string is case-insensitively equal
Expand Down Expand Up @@ -200,3 +201,17 @@ func directoryInPath(dirName string) (bool, error) {
}
return false, nil
}

func getAliasesForArchitecture(arch string) []string {
archAliases := map[string][]string{
"amd64": {"x86_64"},
}
return archAliases[strings.ToLower(arch)]
}

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

0 comments on commit 1f6c85d

Please sign in to comment.