Skip to content

Commit

Permalink
clusterctl: fix goproxy to also return versions for major > 1
Browse files Browse the repository at this point in the history
  • Loading branch information
chrischdi committed Dec 8, 2022
1 parent f73b00d commit fb060da
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 41 deletions.
109 changes: 68 additions & 41 deletions cmd/clusterctl/client/repository/goproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package repository

import (
"context"
"fmt"
"io"
"net/http"
"net/url"
Expand Down Expand Up @@ -50,57 +51,83 @@ func newGoproxyClient(scheme, host string) *goproxyClient {
func (g *goproxyClient) getVersions(ctx context.Context, base, owner, repository string) ([]string, error) {
// A goproxy is also able to handle the github repository path instead of the actual go module name.
gomodulePath := path.Join(base, owner, repository)
parsedVersions := semver.Versions{}

rawURL := url.URL{
Scheme: g.scheme,
Host: g.host,
Path: path.Join(gomodulePath, "@v", "/list"),
}

req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL.String(), http.NoBody)
if err != nil {
return nil, errors.Wrapf(err, "failed to get versions: failed to create request")
}

var rawResponse []byte
var retryError error
_ = wait.PollImmediateWithContext(ctx, retryableOperationInterval, retryableOperationTimeout, func(ctx context.Context) (bool, error) {
retryError = nil
majorVersionNumber := 1
var majorVersion string
for {
if majorVersionNumber > 1 {
majorVersion = fmt.Sprintf("v%d", majorVersionNumber)
}
rawURL := url.URL{
Scheme: g.scheme,
Host: g.host,
Path: path.Join(gomodulePath, majorVersion, "@v", "/list"),
}
majorVersionNumber++

resp, err := http.DefaultClient.Do(req)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL.String(), http.NoBody)
if err != nil {
retryError = errors.Wrapf(err, "failed to get versions: failed to do request")
return false, nil
return nil, errors.Wrapf(err, "failed to get versions: failed to create request")
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
retryError = errors.Errorf("failed to get versions: response status code %d", resp.StatusCode)
return false, nil
var rawResponse []byte
var responseStatusCode int
var retryError error
_ = wait.PollImmediateWithContext(ctx, retryableOperationInterval, retryableOperationTimeout, func(ctx context.Context) (bool, error) {
retryError = nil

resp, err := http.DefaultClient.Do(req)
if err != nil {
retryError = errors.Wrapf(err, "failed to get versions: failed to do request")
return false, nil
}
defer resp.Body.Close()

responseStatusCode = resp.StatusCode

// Status codes OK and NotFound are expected results:
// * OK indicates that we got a list of versions to read.
// * NotFound indicates that there are no versions for this module / modules major version.
if responseStatusCode != http.StatusOK && responseStatusCode != http.StatusNotFound {
retryError = errors.Errorf("failed to get versions: response status code %d", resp.StatusCode)
return false, nil
}

// only read the response for http.StatusOK
if responseStatusCode == http.StatusOK {
rawResponse, err = io.ReadAll(resp.Body)
if err != nil {
retryError = errors.Wrap(err, "failed to get versions: error reading goproxy response body")
return false, nil
}
}
return true, nil
})
if retryError != nil {
return nil, retryError
}

rawResponse, err = io.ReadAll(resp.Body)
if err != nil {
retryError = errors.Wrap(err, "failed to get versions: error reading goproxy response body")
return false, nil
// Don't try to read the versions if status was not found.
if responseStatusCode == http.StatusNotFound {
break
}
return true, nil
})
if retryError != nil {
return nil, retryError
}

parsedVersions := semver.Versions{}
for _, s := range strings.Split(string(rawResponse), "\n") {
if s == "" {
continue
}
parsedVersion, err := semver.ParseTolerant(s)
if err != nil {
// Discard releases with tags that are not a valid semantic versions (the user can point explicitly to such releases).
continue
for _, s := range strings.Split(string(rawResponse), "\n") {
if s == "" {
continue
}
parsedVersion, err := semver.ParseTolerant(s)
if err != nil {
// Discard releases with tags that are not a valid semantic versions (the user can point explicitly to such releases).
continue
}
parsedVersions = append(parsedVersions, parsedVersion)
}
parsedVersions = append(parsedVersions, parsedVersion)
}

if len(parsedVersions) == 0 {
return nil, fmt.Errorf("no versions found for go module %q", gomodulePath)
}

sort.Sort(parsedVersions)
Expand Down
21 changes: 21 additions & 0 deletions cmd/clusterctl/client/repository/repository_github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ func Test_gitHubRepository_GetVersions(t *testing.T) {
fmt.Fprint(w, "v0.3.1\n")
})

// setup an handler for returning 3 different major fake releases
muxGoproxy.HandleFunc("/github.com/o/r3/@v/list", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, "v1.0.0\n")
fmt.Fprint(w, "v0.1.0\n")
})
muxGoproxy.HandleFunc("/github.com/o/r3/v2/@v/list", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, "v2.0.0\n")
})
muxGoproxy.HandleFunc("/github.com/o/r3/v3/@v/list", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, "v3.0.0\n")
})

configVariablesClient := test.NewFakeVariableClient()

tests := []struct {
Expand All @@ -83,6 +98,12 @@ func Test_gitHubRepository_GetVersions(t *testing.T) {
want: []string{"v0.3.1", "v0.3.2", "v0.4.0", "v0.5.0"},
wantErr: false,
},
{
name: "use goproxy having multiple majors",
providerConfig: config.NewProvider("test", "https://github.com/o/r3/releases/v3.0.0/path", clusterctlv1.CoreProviderType),
want: []string{"v0.1.0", "v1.0.0", "v2.0.0", "v3.0.0"},
wantErr: false,
},
{
name: "failure",
providerConfig: config.NewProvider("test", "https://github.com/o/unknown/releases/v0.4.0/path", clusterctlv1.CoreProviderType),
Expand Down

0 comments on commit fb060da

Please sign in to comment.