Skip to content

Commit 4867dad

Browse files
authored
Merge pull request #9237 from fabriziopandini/cluster-goproxy-version-check
🌱 use goproxy to check version in clusterctl
2 parents 735e904 + a2afb25 commit 4867dad

File tree

2 files changed

+82
-18
lines changed

2 files changed

+82
-18
lines changed

cmd/clusterctl/cmd/version_checker.go

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"os"
23+
"path"
2324
"path/filepath"
2425
"regexp"
2526
"strings"
@@ -34,6 +35,7 @@ import (
3435

3536
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
3637
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
38+
"sigs.k8s.io/cluster-api/internal/goproxy"
3739
"sigs.k8s.io/cluster-api/version"
3840
)
3941

@@ -47,21 +49,27 @@ type versionChecker struct {
4749
versionFilePath string
4850
cliVersion func() version.Info
4951
githubClient *github.Client
52+
goproxyClient *goproxy.Client
5053
}
5154

5255
// newVersionChecker returns a versionChecker. Its behavior has been inspired
5356
// by https://github.com/cli/cli.
5457
func newVersionChecker(ctx context.Context, vc config.VariablesClient) (*versionChecker, error) {
55-
var client *github.Client
58+
var githubClient *github.Client
5659
token, err := vc.Get("GITHUB_TOKEN")
5760
if err == nil {
5861
ts := oauth2.StaticTokenSource(
5962
&oauth2.Token{AccessToken: token},
6063
)
6164
tc := oauth2.NewClient(ctx, ts)
62-
client = github.NewClient(tc)
65+
githubClient = github.NewClient(tc)
6366
} else {
64-
client = github.NewClient(nil)
67+
githubClient = github.NewClient(nil)
68+
}
69+
70+
var goproxyClient *goproxy.Client
71+
if scheme, host, err := goproxy.GetSchemeAndHost(os.Getenv("GOPROXY")); err == nil && scheme != "" && host != "" {
72+
goproxyClient = goproxy.NewClient(scheme, host)
6573
}
6674

6775
configDirectory, err := xdg.ConfigFile(config.ConfigFolderXDG)
@@ -72,7 +80,8 @@ func newVersionChecker(ctx context.Context, vc config.VariablesClient) (*version
7280
return &versionChecker{
7381
versionFilePath: filepath.Join(configDirectory, "version.yaml"),
7482
cliVersion: version.Get,
75-
githubClient: client,
83+
githubClient: githubClient,
84+
goproxyClient: goproxyClient,
7685
}, nil
7786
}
7887

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

140149
func (v *versionChecker) getLatestRelease(ctx context.Context) (*ReleaseInfo, error) {
141150
log := logf.Log
151+
152+
// Try to get latest clusterctl version number from the local state file.
153+
// NOTE: local state file is ignored if older than 1d.
142154
vs, err := readStateFile(v.versionFilePath)
143155
if err != nil {
144156
return nil, errors.Wrap(err, "unable to read version state file")
145157
}
158+
if vs != nil {
159+
return &vs.LatestRelease, nil
160+
}
146161

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

157-
vs = &VersionState{
158-
LastCheck: time.Now(),
159-
LatestRelease: ReleaseInfo{
160-
Version: release.GetTagName(),
161-
URL: release.GetHTMLURL(),
162-
},
173+
if err := writeStateFile(v.versionFilePath, vs); err != nil {
174+
return nil, errors.Wrap(err, "unable to write version state file")
163175
}
176+
return &vs.LatestRelease, nil
177+
}
178+
179+
// Otherwise fall back to get latest clusterctl version number from GitHub.
180+
latest, err = v.gitHubGetLatest(ctx)
181+
if err != nil {
182+
log.V(1).Info("⚠️ Unable to get latest github release for clusterctl")
183+
// failing silently here so we don't error out in air-gapped
184+
// environments.
185+
return nil, nil //nolint:nilerr
186+
}
187+
188+
vs = &VersionState{
189+
LastCheck: time.Now(),
190+
LatestRelease: *latest,
164191
}
165192

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

200+
func (v *versionChecker) goproxyGetLatest(ctx context.Context) (*ReleaseInfo, error) {
201+
if v.goproxyClient == nil {
202+
return nil, nil
203+
}
204+
205+
gomodulePath := path.Join("sigs.k8s.io", "cluster-api")
206+
versions, err := v.goproxyClient.GetVersions(ctx, gomodulePath)
207+
if err != nil {
208+
return nil, err
209+
}
210+
211+
latest := semver.Version{}
212+
for _, v := range versions {
213+
if v.GT(latest) {
214+
latest = v
215+
}
216+
}
217+
return &ReleaseInfo{
218+
Version: latest.String(),
219+
URL: gomodulePath,
220+
}, nil
221+
}
222+
223+
func (v *versionChecker) gitHubGetLatest(ctx context.Context) (*ReleaseInfo, error) {
224+
release, _, err := v.githubClient.Repositories.GetLatestRelease(ctx, "kubernetes-sigs", "cluster-api")
225+
if err != nil {
226+
return nil, err
227+
}
228+
return &ReleaseInfo{
229+
Version: release.GetTagName(),
230+
URL: release.GetHTMLURL(),
231+
}, nil
232+
}
233+
173234
func writeStateFile(path string, vs *VersionState) error {
174235
vsb, err := yaml.Marshal(vs)
175236
if err != nil {

cmd/clusterctl/cmd/version_checker_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ https://github.com/foo/bar/releases/v0.3.8-alpha.1
257257

258258
versionChecker.cliVersion = tt.cliVersion
259259
versionChecker.githubClient = fakeGithubClient
260+
versionChecker.goproxyClient = nil
260261
versionChecker.versionFilePath = tmpVersionFile
261262

262263
output, err := versionChecker.Check(ctx)
@@ -327,6 +328,7 @@ func TestVersionChecker_ReadFromStateFile(t *testing.T) {
327328
g.Expect(err).ToNot(HaveOccurred())
328329
versionChecker.versionFilePath = tmpVersionFile
329330
versionChecker.githubClient = fakeGithubClient1
331+
versionChecker.goproxyClient = nil
330332

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

390393
_, err = versionChecker.getLatestRelease(ctx)
391394
g.Expect(err).ToNot(HaveOccurred())

0 commit comments

Comments
 (0)