Skip to content

Commit e3f73a6

Browse files
committed
return error if gh client returns 404 to bubble up the workaround
1 parent 9ad6d51 commit e3f73a6

File tree

3 files changed

+52
-8
lines changed

3 files changed

+52
-8
lines changed

cmd/clusterctl/client/repository/repository_github.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ const (
5050
)
5151

5252
var (
53+
errNotFound = errors.New("404 Not Found")
54+
5355
// Caches used to limit the number of GitHub API calls.
5456

5557
cacheVersions = map[string][]string{}
@@ -156,6 +158,11 @@ func (g *gitHubRepository) ComponentsPath() string {
156158
func (g *gitHubRepository) GetFile(version, path string) ([]byte, error) {
157159
release, err := g.getReleaseByTag(version)
158160
if err != nil {
161+
if errors.Is(err, errNotFound) {
162+
// If it was ErrNotFound, then there is no release yet for the resolved tag.
163+
// Ref: https://github.com/kubernetes-sigs/cluster-api/issues/7889
164+
return nil, errors.Wrapf(err, "release not found for version %s, please retry later or set \"GOPROXY=off\" to get the current stable release", version)
165+
}
159166
return nil, errors.Wrapf(err, "failed to get GitHub release %s", version)
160167
}
161168

@@ -227,7 +234,7 @@ func NewGitHubRepository(providerConfig config.Provider, configVariablesClient c
227234
if defaultVersion == githubLatestReleaseLabel {
228235
repo.defaultVersion, err = latestContractRelease(repo, clusterv1.GroupVersion.Version)
229236
if err != nil {
230-
return nil, errors.Wrap(err, "failed to get GitHub latest version")
237+
return nil, errors.Wrap(err, "failed to get latest release")
231238
}
232239
}
233240

@@ -351,6 +358,10 @@ func (g *gitHubRepository) getReleaseByTag(tag string) (*github.RepositoryReleas
351358
release, _, getReleasesErr = client.Repositories.GetReleaseByTag(context.TODO(), g.owner, g.repository, tag)
352359
if getReleasesErr != nil {
353360
retryError = g.handleGithubErr(getReleasesErr, "failed to read release %q", tag)
361+
// Return immediately if not found
362+
if errors.Is(retryError, errNotFound) {
363+
return false, retryError
364+
}
354365
// Return immediately if we are rate limited.
355366
if _, ok := getReleasesErr.(*github.RateLimitError); ok {
356367
return false, retryError
@@ -437,5 +448,10 @@ func (g *gitHubRepository) handleGithubErr(err error, message string, args ...in
437448
if _, ok := err.(*github.RateLimitError); ok {
438449
return errors.New("rate limit for github api has been reached. Please wait one hour or get a personal API token and assign it to the GITHUB_TOKEN environment variable")
439450
}
451+
if ghErr, ok := err.(*github.ErrorResponse); ok {
452+
if ghErr.Response.StatusCode == http.StatusNotFound {
453+
return errNotFound
454+
}
455+
}
440456
return errors.Wrapf(err, message, args...)
441457
}

cmd/clusterctl/client/repository/repository_github_test.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,15 @@ func Test_gitHubRepository_getLatestContractRelease(t *testing.T) {
441441
fmt.Fprint(w, "v0.3.1\n")
442442
})
443443

444+
// setup an handler for returning 4 fake releases but no actual tagged release
445+
muxGoproxy.HandleFunc("/github.com/o/r2/@v/list", func(w http.ResponseWriter, r *http.Request) {
446+
testMethod(t, r, "GET")
447+
fmt.Fprint(w, "v0.5.0\n")
448+
fmt.Fprint(w, "v0.4.0\n")
449+
fmt.Fprint(w, "v0.3.2\n")
450+
fmt.Fprint(w, "v0.3.1\n")
451+
})
452+
444453
configVariablesClient := test.NewFakeVariableClient()
445454

446455
type field struct {
@@ -480,6 +489,15 @@ func Test_gitHubRepository_getLatestContractRelease(t *testing.T) {
480489
contract: "foo",
481490
wantErr: false,
482491
},
492+
{
493+
name: "Return 404 if there is no release for the tag",
494+
field: field{
495+
providerConfig: config.NewProvider("test", "https://github.com/o/r2/releases/v0.99.0/path", clusterctlv1.CoreProviderType),
496+
},
497+
want: "0.99.0",
498+
contract: "v1alpha4",
499+
wantErr: true,
500+
},
483501
}
484502
for _, tt := range tests {
485503
t.Run(tt.name, func(t *testing.T) {
@@ -514,6 +532,11 @@ func Test_gitHubRepository_getLatestRelease(t *testing.T) {
514532
fmt.Fprint(w, "v0.4.3-alpha\n") // prerelease
515533
fmt.Fprint(w, "foo\n") // no semantic version tag
516534
})
535+
// And also expose a release for them
536+
muxGoproxy.HandleFunc("/api.github.com/repos/o/r1/releases/tags/v0.4.2", func(w http.ResponseWriter, r *http.Request) {
537+
testMethod(t, r, "GET")
538+
fmt.Fprint(w, "{}\n")
539+
})
517540

518541
// Setup a handler for returning no releases.
519542
muxGoproxy.HandleFunc("/github.com/o/r2/@v/list", func(w http.ResponseWriter, r *http.Request) {
@@ -543,7 +566,7 @@ func Test_gitHubRepository_getLatestRelease(t *testing.T) {
543566
{
544567
name: "Get latest release, ignores pre-release version",
545568
field: field{
546-
providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/latest/path", clusterctlv1.CoreProviderType),
569+
providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.4.2/path", clusterctlv1.CoreProviderType),
547570
},
548571
want: "v0.4.2",
549572
wantErr: false,
@@ -559,7 +582,7 @@ func Test_gitHubRepository_getLatestRelease(t *testing.T) {
559582
{
560583
name: "Falls back to latest prerelease when no official release present",
561584
field: field{
562-
providerConfig: config.NewProvider("test", "https://github.com/o/r3/releases/latest/path", clusterctlv1.CoreProviderType),
585+
providerConfig: config.NewProvider("test", "https://github.com/o/r3/releases/v0.1.0-alpha.2/path", clusterctlv1.CoreProviderType),
563586
},
564587
want: "v0.1.0-alpha.2",
565588
wantErr: false,
@@ -619,7 +642,7 @@ func Test_gitHubRepository_getLatestPatchRelease(t *testing.T) {
619642
{
620643
name: "Get latest patch release, no Major/Minor specified",
621644
field: field{
622-
providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/latest/path", clusterctlv1.CoreProviderType),
645+
providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v1.3.2/path", clusterctlv1.CoreProviderType),
623646
},
624647
minor: nil,
625648
major: nil,
@@ -629,7 +652,7 @@ func Test_gitHubRepository_getLatestPatchRelease(t *testing.T) {
629652
{
630653
name: "Get latest patch release, for Major 0 and Minor 3",
631654
field: field{
632-
providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/latest/path", clusterctlv1.CoreProviderType),
655+
providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.3.2/path", clusterctlv1.CoreProviderType),
633656
},
634657
major: &major0,
635658
minor: &minor3,
@@ -639,7 +662,7 @@ func Test_gitHubRepository_getLatestPatchRelease(t *testing.T) {
639662
{
640663
name: "Get latest patch release, for Major 0 and Minor 4",
641664
field: field{
642-
providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/latest/path", clusterctlv1.CoreProviderType),
665+
providerConfig: config.NewProvider("test", "https://github.com/o/r1/releases/v0.4.0/path", clusterctlv1.CoreProviderType),
643666
},
644667
major: &major0,
645668
minor: &minor4,

cmd/clusterctl/client/repository/repository_versions.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,16 @@ func latestContractRelease(repo Repository, contract string) (string, error) {
3939
}
4040
// Attempt to check if the latest release satisfies the API Contract
4141
// This is a best-effort attempt to find the latest release for an older API contract if it's not the latest release.
42-
// If an error occurs, we just return the latest release.
4342
file, err := repo.GetFile(latest, metadataFile)
43+
// If an error occurs, we just return the latest release.
4444
if err != nil {
45+
if errors.Is(err, errNotFound) {
46+
// If it was ErrNotFound, then there is no release yet for the resolved tag.
47+
// Ref: https://github.com/kubernetes-sigs/cluster-api/issues/7889
48+
return "", err
49+
}
4550
// if we can't get the metadata file from the release, we return latest.
46-
return latest, nil //nolint:nilerr
51+
return latest, nil
4752
}
4853
latestMetadata := &clusterctlv1.Metadata{}
4954
codecFactory := serializer.NewCodecFactory(scheme.Scheme)

0 commit comments

Comments
 (0)