Skip to content

Commit

Permalink
Merge pull request #2316 from neogopher/backport/v0.19/pr-2309
Browse files Browse the repository at this point in the history
[v0.19] bugfix(cli): change process of finding specified binary releases
  • Loading branch information
ThomasK33 authored Dec 6, 2024
2 parents 418ded4 + 296c678 commit 4403ec7
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 14 deletions.
10 changes: 7 additions & 3 deletions cmd/vclusterctl/cmd/upgrade.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cmd

import (
"context"

"github.com/loft-sh/log"
"github.com/loft-sh/vcluster/pkg/upgrade"
"github.com/pkg/errors"
Expand Down Expand Up @@ -29,16 +31,18 @@ func NewUpgradeCmd() *cobra.Command {
Upgrades the vcluster CLI to the newest version
#######################################################`,
Args: cobra.NoArgs,
RunE: cmd.Run,
RunE: func(cobraCmd *cobra.Command, _ []string) error {
return cmd.Run(cobraCmd.Context())
},
}

upgradeCmd.Flags().StringVar(&cmd.Version, "version", "", "The version to update vcluster to. Defaults to the latest stable version available")
return upgradeCmd
}

// Run executes the command logic
func (cmd *UpgradeCmd) Run(*cobra.Command, []string) error {
err := upgrade.Upgrade(cmd.Version, cmd.log)
func (cmd *UpgradeCmd) Run(ctx context.Context) error {
err := upgrade.Upgrade(ctx, cmd.Version, cmd.log)
if err != nil {
return errors.Errorf("Couldn't upgrade: %v", err)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/controllers/resources/nodes/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,10 @@ func (s *nodeSyncer) ModifyController(ctx *synccontext.RegisterContext, bld *bui
}

// only used when scheduler is enabled
func enqueueNonVclusterPod(old, new client.Object, q workqueue.RateLimitingInterface) {
pod, ok := new.(*corev1.Pod)
func enqueueNonVclusterPod(old, newObj client.Object, q workqueue.RateLimitingInterface) {
pod, ok := newObj.(*corev1.Pod)
if !ok {
klog.Errorf("invalid type passed to pod handler: %T", new)
klog.Errorf("invalid type passed to pod handler: %T", newObj)
return
}
// skip if node name missing
Expand Down
4 changes: 2 additions & 2 deletions pkg/helm/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ func ParseInLocation(layout, value string, loc *time.Location) (Time, error) {
return Time{Time: t}, err
}

func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time {
return Time{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)}
func Date(year int, month time.Month, day, hour, minute, sec, nsec int, loc *time.Location) Time {
return Time{Time: time.Date(year, month, day, hour, minute, sec, nsec, loc)}
}

func Unix(sec int64, nsec int64) Time { return Time{Time: time.Unix(sec, nsec)} }
Expand Down
105 changes: 105 additions & 0 deletions pkg/upgrade/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package upgrade

import (
"context"
"fmt"
"net/http"
"os"
"regexp"
"runtime"
"strings"

"github.com/blang/semver"
"github.com/google/go-github/v30/github"
"github.com/pkg/errors"
gitconfig "github.com/tcnksm/go-gitconfig"
"golang.org/x/oauth2"
)

func fetchReleaseByTag(ctx context.Context, owner, repo, tag string) (*github.RepositoryRelease, error) {
var (
token string
hc *http.Client

release *github.RepositoryRelease
)

if os.Getenv("GITHUB_TOKEN") != "" {
token = os.Getenv("GITHUB_TOKEN")
}
if token == "" {
token, _ = gitconfig.GithubToken()
}

if token == "" {
hc = http.DefaultClient
} else {
src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
hc = oauth2.NewClient(ctx, src)
}

client := github.NewClient(hc)

// Fetch the release by tag
release, _, err := client.Repositories.GetReleaseByTag(ctx, owner, repo, tag)
if err != nil {
return release, fmt.Errorf("error fetching release by tag: %w", err)
}

return release, nil
}

func findAssetFromRelease(rel *github.RepositoryRelease) (*github.ReleaseAsset, semver.Version, bool, error) {
// Generate candidates
suffixes := make([]string, 0, 2*7*2)
for _, sep := range []rune{'_', '-'} {
for _, ext := range []string{".zip", ".tar.gz", ".tgz", ".gzip", ".gz", ".tar.xz", ".xz", ""} {
suffix := fmt.Sprintf("%s%c%s%s", runtime.GOOS, sep, runtime.GOARCH, ext)
suffixes = append(suffixes, suffix)
if runtime.GOOS == "windows" {
suffix = fmt.Sprintf("%s%c%s.exe%s", runtime.GOOS, sep, runtime.GOARCH, ext)
suffixes = append(suffixes, suffix)
}
}
}

verText := rel.GetTagName()
indices := reVersion.FindStringIndex(verText)
if indices == nil {
return nil, semver.Version{}, false, fmt.Errorf("skip version not adopting semver %s", verText)
}
if indices[0] > 0 {
// Strip prefix of version
verText = verText[indices[0]:]
}

// If semver cannot parse the version text, it means that the text is not adopting
// the semantic versioning. So it should be skipped.
ver, err := semver.Make(verText)
if err != nil {
return nil, semver.Version{}, false, fmt.Errorf("failed to parse a semantic version %s", verText)
}

filterRe, err := regexp.Compile("vcluster")
if err != nil {
return nil, semver.Version{}, false, errors.New("failed to compile regexp")
}

for _, asset := range rel.Assets {
name := asset.GetName()

// Skipping asset not matching filter
if !filterRe.MatchString(name) {
continue
}

for _, s := range suffixes {
if strings.HasSuffix(name, s) { // require version, arch etc
// default: assume single artifact
return asset, ver, true, nil
}
}
}

return nil, semver.Version{}, false, fmt.Errorf("no suitable asset was found in release %s", rel.GetTagName())
}
46 changes: 44 additions & 2 deletions pkg/upgrade/upgrade.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package upgrade

import (
"context"
"fmt"
"os"
"regexp"
"strings"
"sync"

"github.com/loft-sh/log"
Expand Down Expand Up @@ -112,15 +114,15 @@ func NewerVersionAvailable() string {
}

// Upgrade downloads the latest release from github and replaces vcluster if a new version is found
func Upgrade(flagVersion string, log log.Logger) error {
func Upgrade(ctx context.Context, flagVersion string, log log.Logger) error {
updater, err := selfupdate.NewUpdater(selfupdate.Config{
Filters: []string{"vcluster"},
})
if err != nil {
return fmt.Errorf("failed to initialize updater: %w", err)
}
if flagVersion != "" {
release, found, err := updater.DetectVersion(githubSlug, flagVersion)
release, found, err := DetectVersion(ctx, githubSlug, flagVersion)
if err != nil {
return errors.Wrap(err, "find version")
} else if !found {
Expand Down Expand Up @@ -169,3 +171,43 @@ func Upgrade(flagVersion string, log log.Logger) error {

return nil
}

func DetectVersion(ctx context.Context, slug string, version string) (*selfupdate.Release, bool, error) {
var (
release *selfupdate.Release
found bool
err error
)

repo := strings.Split(slug, "/")
if len(repo) != 2 || repo[0] == "" || repo[1] == "" {
return nil, false, fmt.Errorf("invalid slug format. It should be 'owner/name': %s", slug)
}

githubRelease, err := fetchReleaseByTag(ctx, repo[0], repo[1], version)
if err != nil {
return nil, false, fmt.Errorf("repository or release not found: %w", err)
}

asset, semVer, found, err := findAssetFromRelease(githubRelease)
if !found {
return nil, false, fmt.Errorf("release asset not found: %w", err)
}

publishedAt := githubRelease.GetPublishedAt().Time
release = &selfupdate.Release{
Version: semVer,
AssetURL: asset.GetBrowserDownloadURL(),
AssetByteSize: asset.GetSize(),
AssetID: asset.GetID(),
ValidationAssetID: -1,
URL: githubRelease.GetHTMLURL(),
ReleaseNotes: githubRelease.GetBody(),
Name: githubRelease.GetName(),
PublishedAt: &publishedAt,
RepoOwner: repo[0],
RepoName: repo[1],
}

return release, true, nil
}
6 changes: 4 additions & 2 deletions pkg/upgrade/upgrade_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package upgrade

import (
"context"
"os"
"strings"
"testing"
Expand Down Expand Up @@ -73,7 +74,8 @@ func TestUpgrade(t *testing.T) {
defer func() { version = versionBackup }()

// Newest version already reached
err = Upgrade("", log.GetInstance())
ctx := context.Background()
err = Upgrade(ctx, "", log.GetInstance())
assert.Equal(t, false, err != nil, "Upgrade returned error if newest version already reached")
err = logFile.Close()
if err != nil {
Expand All @@ -89,6 +91,6 @@ func TestUpgrade(t *testing.T) {
githubSlugBackup := githubSlug
githubSlug = ""
defer func() { githubSlug = githubSlugBackup }()
err = Upgrade("", log.GetInstance())
err = Upgrade(ctx, "", log.GetInstance())
assert.Equal(t, true, err != nil, "No error returned if DetectLatest returns one.")
}
4 changes: 2 additions & 2 deletions pkg/util/encoding/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ func Convert(from runtime.Object, to runtime.Object) error {
}

// ConvertList converts the objects from the from list and puts them into the to list
func ConvertList(fromList runtime.Object, toList runtime.Object, new rest.Storage) error {
func ConvertList(fromList runtime.Object, toList runtime.Object, newObj rest.Storage) error {
list, err := meta.ExtractList(fromList)
if err != nil {
return err
}

newItems := []runtime.Object{}
for _, item := range list {
newItem := new.New()
newItem := newObj.New()
err = Convert(item, newItem)
if err != nil {
return err
Expand Down

0 comments on commit 4403ec7

Please sign in to comment.