From 16031f226c812e65da5d890e8bfc0ad9ba90ce06 Mon Sep 17 00:00:00 2001
From: neogopher <sachin.lobo@loft.sh>
Date: Tue, 3 Dec 2024 20:12:59 +0530
Subject: [PATCH 1/3] bugfix(cli): change process of finding specified binary
 releases

---
 pkg/upgrade/helpers.go | 106 +++++++++++++++++++++++++++++++++++++++++
 pkg/upgrade/upgrade.go |  43 ++++++++++++++++-
 2 files changed, 148 insertions(+), 1 deletion(-)
 create mode 100644 pkg/upgrade/helpers.go

diff --git a/pkg/upgrade/helpers.go b/pkg/upgrade/helpers.go
new file mode 100644
index 0000000000..dd517379ea
--- /dev/null
+++ b/pkg/upgrade/helpers.go
@@ -0,0 +1,106 @@
+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(owner, repo, tag string) (*github.RepositoryRelease, error) {
+	var (
+		ctx   = context.Background()
+		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())
+}
diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go
index d2416749e7..b5c95c47be 100644
--- a/pkg/upgrade/upgrade.go
+++ b/pkg/upgrade/upgrade.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"os"
 	"regexp"
+	"strings"
 	"sync"
 
 	"github.com/loft-sh/log"
@@ -120,7 +121,7 @@ func Upgrade(flagVersion string, log log.Logger) error {
 		return fmt.Errorf("failed to initialize updater: %w", err)
 	}
 	if flagVersion != "" {
-		release, found, err := updater.DetectVersion(githubSlug, flagVersion)
+		release, found, err := DetectVersion(githubSlug, flagVersion)
 		if err != nil {
 			return errors.Wrap(err, "find version")
 		} else if !found {
@@ -169,3 +170,43 @@ func Upgrade(flagVersion string, log log.Logger) error {
 
 	return nil
 }
+
+func DetectVersion(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(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
+}

From d59e7d4107a4c45487b2c4bb8b9e7674a7a56bf1 Mon Sep 17 00:00:00 2001
From: neogopher <sachin.lobo@loft.sh>
Date: Wed, 4 Dec 2024 17:51:45 +0530
Subject: [PATCH 2/3] use CobraCmd context in the upgrade flow

---
 cmd/vclusterctl/cmd/upgrade.go | 10 +++++++---
 pkg/upgrade/helpers.go         |  3 +--
 pkg/upgrade/upgrade.go         |  9 +++++----
 pkg/upgrade/upgrade_test.go    |  6 ++++--
 4 files changed, 17 insertions(+), 11 deletions(-)

diff --git a/cmd/vclusterctl/cmd/upgrade.go b/cmd/vclusterctl/cmd/upgrade.go
index 2ea886331b..50e4f8085e 100644
--- a/cmd/vclusterctl/cmd/upgrade.go
+++ b/cmd/vclusterctl/cmd/upgrade.go
@@ -1,6 +1,8 @@
 package cmd
 
 import (
+	"context"
+
 	"github.com/loft-sh/log"
 	"github.com/loft-sh/vcluster/pkg/upgrade"
 	"github.com/pkg/errors"
@@ -29,7 +31,9 @@ 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")
@@ -37,8 +41,8 @@ Upgrades the vcluster CLI to the newest version
 }
 
 // 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)
 	}
diff --git a/pkg/upgrade/helpers.go b/pkg/upgrade/helpers.go
index dd517379ea..388f134969 100644
--- a/pkg/upgrade/helpers.go
+++ b/pkg/upgrade/helpers.go
@@ -16,9 +16,8 @@ import (
 	"golang.org/x/oauth2"
 )
 
-func fetchReleaseByTag(owner, repo, tag string) (*github.RepositoryRelease, error) {
+func fetchReleaseByTag(ctx context.Context, owner, repo, tag string) (*github.RepositoryRelease, error) {
 	var (
-		ctx   = context.Background()
 		token string
 		hc    *http.Client
 
diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go
index b5c95c47be..f929dc1cb9 100644
--- a/pkg/upgrade/upgrade.go
+++ b/pkg/upgrade/upgrade.go
@@ -1,6 +1,7 @@
 package upgrade
 
 import (
+	"context"
 	"fmt"
 	"os"
 	"regexp"
@@ -113,7 +114,7 @@ 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"},
 	})
@@ -121,7 +122,7 @@ func Upgrade(flagVersion string, log log.Logger) error {
 		return fmt.Errorf("failed to initialize updater: %w", err)
 	}
 	if flagVersion != "" {
-		release, found, err := DetectVersion(githubSlug, flagVersion)
+		release, found, err := DetectVersion(ctx, githubSlug, flagVersion)
 		if err != nil {
 			return errors.Wrap(err, "find version")
 		} else if !found {
@@ -171,7 +172,7 @@ func Upgrade(flagVersion string, log log.Logger) error {
 	return nil
 }
 
-func DetectVersion(slug string, version string) (*selfupdate.Release, bool, error) {
+func DetectVersion(ctx context.Context, slug string, version string) (*selfupdate.Release, bool, error) {
 	var (
 		release *selfupdate.Release
 		found   bool
@@ -183,7 +184,7 @@ func DetectVersion(slug string, version string) (*selfupdate.Release, bool, erro
 		return nil, false, fmt.Errorf("invalid slug format. It should be 'owner/name': %s", slug)
 	}
 
-	githubRelease, err := fetchReleaseByTag(repo[0], repo[1], version)
+	githubRelease, err := fetchReleaseByTag(ctx, repo[0], repo[1], version)
 	if err != nil {
 		return nil, false, fmt.Errorf("repository or release not found: %w", err)
 	}
diff --git a/pkg/upgrade/upgrade_test.go b/pkg/upgrade/upgrade_test.go
index 3c0df6fb58..86e86ebe73 100644
--- a/pkg/upgrade/upgrade_test.go
+++ b/pkg/upgrade/upgrade_test.go
@@ -1,6 +1,7 @@
 package upgrade
 
 import (
+	"context"
 	"os"
 	"strings"
 	"testing"
@@ -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 {
@@ -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.")
 }

From 296c6781784d8a0399bfeaf2b94c46b2aaf6e0ab Mon Sep 17 00:00:00 2001
From: neogopher <sachin.lobo@loft.sh>
Date: Thu, 5 Dec 2024 13:29:20 +0530
Subject: [PATCH 3/3] fix lint errors

---
 pkg/controllers/resources/nodes/syncer.go | 6 +++---
 pkg/helm/time.go                          | 4 ++--
 pkg/util/encoding/helper.go               | 4 ++--
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/pkg/controllers/resources/nodes/syncer.go b/pkg/controllers/resources/nodes/syncer.go
index 8e61599f35..938497b8d6 100644
--- a/pkg/controllers/resources/nodes/syncer.go
+++ b/pkg/controllers/resources/nodes/syncer.go
@@ -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
diff --git a/pkg/helm/time.go b/pkg/helm/time.go
index 3eeeeccc01..00acfaf386 100644
--- a/pkg/helm/time.go
+++ b/pkg/helm/time.go
@@ -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)} }
diff --git a/pkg/util/encoding/helper.go b/pkg/util/encoding/helper.go
index d3aa23211e..114cb64504 100644
--- a/pkg/util/encoding/helper.go
+++ b/pkg/util/encoding/helper.go
@@ -31,7 +31,7 @@ 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
@@ -39,7 +39,7 @@ func ConvertList(fromList runtime.Object, toList runtime.Object, new rest.Storag
 
 	newItems := []runtime.Object{}
 	for _, item := range list {
-		newItem := new.New()
+		newItem := newObj.New()
 		err = Convert(item, newItem)
 		if err != nil {
 			return err