Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🌱 use goproxy to check version in clusterctl #9237

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
fabriziopandini marked this conversation as resolved.
Show resolved Hide resolved
}

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,
fabriziopandini marked this conversation as resolved.
Show resolved Hide resolved
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.
sbueringer marked this conversation as resolved.
Show resolved Hide resolved
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
sbueringer marked this conversation as resolved.
Show resolved Hide resolved
}

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