Skip to content

Commit

Permalink
use goproxy to check version in clusterctl
Browse files Browse the repository at this point in the history
  • Loading branch information
fabriziopandini authored and sbueringer committed Aug 28, 2023
1 parent 82eff49 commit a2afb25
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 18 deletions.
97 changes: 79 additions & 18 deletions cmd/clusterctl/cmd/version_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"strings"
Expand All @@ -34,6 +35,7 @@ import (

"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
"sigs.k8s.io/cluster-api/internal/goproxy"
"sigs.k8s.io/cluster-api/version"
)

Expand All @@ -47,21 +49,27 @@ type versionChecker struct {
versionFilePath string
cliVersion func() version.Info
githubClient *github.Client
goproxyClient *goproxy.Client
}

// newVersionChecker returns a versionChecker. Its behavior has been inspired
// by https://github.com/cli/cli.
func newVersionChecker(ctx context.Context, vc config.VariablesClient) (*versionChecker, error) {
var client *github.Client
var githubClient *github.Client
token, err := vc.Get("GITHUB_TOKEN")
if err == nil {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
tc := oauth2.NewClient(ctx, ts)
client = github.NewClient(tc)
githubClient = github.NewClient(tc)
} else {
client = github.NewClient(nil)
githubClient = github.NewClient(nil)
}

var goproxyClient *goproxy.Client
if scheme, host, err := goproxy.GetSchemeAndHost(os.Getenv("GOPROXY")); err == nil && scheme != "" && host != "" {
goproxyClient = goproxy.NewClient(scheme, host)
}

configDirectory, err := xdg.ConfigFile(config.ConfigFolderXDG)
Expand All @@ -72,7 +80,8 @@ func newVersionChecker(ctx context.Context, vc config.VariablesClient) (*version
return &versionChecker{
versionFilePath: filepath.Join(configDirectory, "version.yaml"),
cliVersion: version.Get,
githubClient: client,
githubClient: githubClient,
goproxyClient: goproxyClient,
}, nil
}

Expand Down Expand Up @@ -139,28 +148,46 @@ New clusterctl version available: v%s -> v%s

func (v *versionChecker) getLatestRelease(ctx context.Context) (*ReleaseInfo, error) {
log := logf.Log

// Try to get latest clusterctl version number from the local state file.
// NOTE: local state file is ignored if older than 1d.
vs, err := readStateFile(v.versionFilePath)
if err != nil {
return nil, errors.Wrap(err, "unable to read version state file")
}
if vs != nil {
return &vs.LatestRelease, nil
}

// if there is no release info in the state file, pull latest release from github
if vs == nil {
release, _, err := v.githubClient.Repositories.GetLatestRelease(ctx, "kubernetes-sigs", "cluster-api")
if err != nil {
log.V(1).Info("⚠️ Unable to get latest github release for clusterctl")
// failing silently here so we don't error out in air-gapped
// environments.
return nil, nil //nolint:nilerr
// Try to get latest clusterctl version number from go modules.
latest, err := v.goproxyGetLatest(ctx)
if err != nil {
log.V(5).Info("error using Goproxy client to get latest versions for clusterctl, falling back to github client")
}
if latest != nil {
vs = &VersionState{
LastCheck: time.Now(),
LatestRelease: *latest,
}

vs = &VersionState{
LastCheck: time.Now(),
LatestRelease: ReleaseInfo{
Version: release.GetTagName(),
URL: release.GetHTMLURL(),
},
if err := writeStateFile(v.versionFilePath, vs); err != nil {
return nil, errors.Wrap(err, "unable to write version state file")
}
return &vs.LatestRelease, nil
}

// Otherwise fall back to get latest clusterctl version number from GitHub.
latest, err = v.gitHubGetLatest(ctx)
if err != nil {
log.V(1).Info("⚠️ Unable to get latest github release for clusterctl")
// failing silently here so we don't error out in air-gapped
// environments.
return nil, nil //nolint:nilerr
}

vs = &VersionState{
LastCheck: time.Now(),
LatestRelease: *latest,
}

if err := writeStateFile(v.versionFilePath, vs); err != nil {
Expand All @@ -170,6 +197,40 @@ func (v *versionChecker) getLatestRelease(ctx context.Context) (*ReleaseInfo, er
return &vs.LatestRelease, nil
}

func (v *versionChecker) goproxyGetLatest(ctx context.Context) (*ReleaseInfo, error) {
if v.goproxyClient == nil {
return nil, nil
}

gomodulePath := path.Join("sigs.k8s.io", "cluster-api")
versions, err := v.goproxyClient.GetVersions(ctx, gomodulePath)
if err != nil {
return nil, err
}

latest := semver.Version{}
for _, v := range versions {
if v.GT(latest) {
latest = v
}
}
return &ReleaseInfo{
Version: latest.String(),
URL: gomodulePath,
}, nil
}

func (v *versionChecker) gitHubGetLatest(ctx context.Context) (*ReleaseInfo, error) {
release, _, err := v.githubClient.Repositories.GetLatestRelease(ctx, "kubernetes-sigs", "cluster-api")
if err != nil {
return nil, err
}
return &ReleaseInfo{
Version: release.GetTagName(),
URL: release.GetHTMLURL(),
}, nil
}

func writeStateFile(path string, vs *VersionState) error {
vsb, err := yaml.Marshal(vs)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions cmd/clusterctl/cmd/version_checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ https://github.com/foo/bar/releases/v0.3.8-alpha.1

versionChecker.cliVersion = tt.cliVersion
versionChecker.githubClient = fakeGithubClient
versionChecker.goproxyClient = nil
versionChecker.versionFilePath = tmpVersionFile

output, err := versionChecker.Check(ctx)
Expand Down Expand Up @@ -327,6 +328,7 @@ func TestVersionChecker_ReadFromStateFile(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
versionChecker.versionFilePath = tmpVersionFile
versionChecker.githubClient = fakeGithubClient1
versionChecker.goproxyClient = nil

// this call to getLatestRelease will pull from our fakeGithubClient1 and
// store the information including timestamp into the state file.
Expand Down Expand Up @@ -386,6 +388,7 @@ func TestVersionChecker_ReadFromStateFileWithin24Hrs(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
versionChecker.versionFilePath = tmpVersionFile
versionChecker.githubClient = fakeGithubClient1
versionChecker.goproxyClient = nil

_, err = versionChecker.getLatestRelease(ctx)
g.Expect(err).ToNot(HaveOccurred())
Expand Down

0 comments on commit a2afb25

Please sign in to comment.