diff --git a/cmd/kubeadm/app/util/version.go b/cmd/kubeadm/app/util/version.go
index 4e90b27dda6..08a68b5c414 100644
--- a/cmd/kubeadm/app/util/version.go
+++ b/cmd/kubeadm/app/util/version.go
@@ -62,6 +62,13 @@ var (
// latest-1 (latest release in 1.x, including alpha/beta)
// latest-1.0 (and similarly 1.1, 1.2, 1.3, ...)
func KubernetesReleaseVersion(version string) (string, error) {
+ return kubernetesReleaseVersion(version, fetchFromURL)
+}
+
+// kubernetesReleaseVersion is a helper function to fetch
+// available version information. Used for testing to eliminate
+// the need for internet calls.
+func kubernetesReleaseVersion(version string, fetcher func(string, time.Duration) (string, error)) (string, error) {
ver := normalizedBuildVersion(version)
if len(ver) != 0 {
return ver, nil
@@ -87,7 +94,7 @@ func KubernetesReleaseVersion(version string) (string, error) {
clientVersion, clientVersionErr := kubeadmVersion(pkgversion.Get().String())
// Fetch version from the internet.
url := fmt.Sprintf("%s/%s.txt", bucketURL, versionLabel)
- body, err := fetchFromURL(url, getReleaseVersionTimeout)
+ body, err := fetcher(url, getReleaseVersionTimeout)
if err != nil {
// If the network operaton was successful but the server did not reply with StatusOK
if body != "" {
@@ -97,18 +104,18 @@ func KubernetesReleaseVersion(version string) (string, error) {
// Handle air-gapped environments by falling back to the client version.
klog.Warningf("could not fetch a Kubernetes version from the internet: %v", err)
klog.Warningf("falling back to the local client version: %s", clientVersion)
- return KubernetesReleaseVersion(clientVersion)
+ return kubernetesReleaseVersion(clientVersion, fetcher)
}
}
if clientVersionErr != nil {
if err != nil {
klog.Warningf("could not obtain neither client nor remote version; fall back to: %s", constants.CurrentKubernetesVersion)
- return KubernetesReleaseVersion(constants.CurrentKubernetesVersion.String())
+ return kubernetesReleaseVersion(constants.CurrentKubernetesVersion.String(), fetcher)
}
klog.Warningf("could not obtain client version; using remote version: %s", body)
- return KubernetesReleaseVersion(body)
+ return kubernetesReleaseVersion(body, fetcher)
}
// both the client and the remote version are obtained; validate them and pick a stable version
@@ -117,7 +124,7 @@ func KubernetesReleaseVersion(version string) (string, error) {
return "", err
}
// Re-validate received version and return.
- return KubernetesReleaseVersion(body)
+ return kubernetesReleaseVersion(body, fetcher)
}
return "", errors.Errorf("version %q doesn't match patterns for neither semantic version nor labels (stable, latest, ...)", version)
}
diff --git a/cmd/kubeadm/app/util/version_test.go b/cmd/kubeadm/app/util/version_test.go
index f40f261338b..48dfd02ec45 100644
--- a/cmd/kubeadm/app/util/version_test.go
+++ b/cmd/kubeadm/app/util/version_test.go
@@ -17,12 +17,12 @@ limitations under the License.
package util
import (
+ "errors"
"fmt"
- "net/http"
- "net/http/httptest"
"path"
"strings"
"testing"
+ "time"
)
func TestEmptyVersion(t *testing.T) {
@@ -50,7 +50,7 @@ func TestValidVersion(t *testing.T) {
}
for _, s := range validVersions {
t.Run(s, func(t *testing.T) {
- ver, err := KubernetesReleaseVersion(s)
+ ver, err := kubernetesReleaseVersion(s, errorFetcher)
t.Log("Valid: ", s, ver, err)
if err != nil {
t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err)
@@ -72,7 +72,7 @@ func TestInvalidVersion(t *testing.T) {
}
for _, s := range invalidVersions {
t.Run(s, func(t *testing.T) {
- ver, err := KubernetesReleaseVersion(s)
+ ver, err := kubernetesReleaseVersion(s, errorFetcher)
t.Log("Invalid: ", s, ver, err)
if err == nil {
t.Errorf("KubernetesReleaseVersion error expected for version %q, but returned successfully", s)
@@ -92,7 +92,7 @@ func TestValidConvenientForUserVersion(t *testing.T) {
}
for _, s := range validVersions {
t.Run(s, func(t *testing.T) {
- ver, err := KubernetesReleaseVersion(s)
+ ver, err := kubernetesReleaseVersion(s, errorFetcher)
t.Log("Valid: ", s, ver, err)
if err != nil {
t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err)
@@ -107,36 +107,36 @@ func TestValidConvenientForUserVersion(t *testing.T) {
func TestVersionFromNetwork(t *testing.T) {
type T struct {
Content string
- Status int
Expected string
ErrorExpected bool
}
cases := map[string]T{
- "stable": {"stable-1", http.StatusOK, "v1.4.6", false}, // recursive pointer to stable-1
- "stable-1": {"v1.4.6", http.StatusOK, "v1.4.6", false},
- "stable-1.3": {"v1.3.10", http.StatusOK, "v1.3.10", false},
- "latest": {"v1.6.0-alpha.0", http.StatusOK, "v1.6.0-alpha.0", false},
- "latest-1.3": {"v1.3.11-beta.0", http.StatusOK, "v1.3.11-beta.0", false},
- "empty": {"", http.StatusOK, "", true},
- "garbage": {"NoSuchKey
The specified key does not exist.", http.StatusOK, "", true},
- "unknown": {"The requested URL was not found on this server.", http.StatusNotFound, "", true},
+ "stable": {"stable-1", "v1.4.6", false}, // recursive pointer to stable-1
+ "stable-1": {"v1.4.6", "v1.4.6", false},
+ "stable-1.3": {"v1.3.10", "v1.3.10", false},
+ "latest": {"v1.6.0-alpha.0", "v1.6.0-alpha.0", false},
+ "latest-1.3": {"v1.3.11-beta.0", "v1.3.11-beta.0", false},
+ "empty": {"", "", true},
+ "garbage": {"NoSuchKey
The specified key does not exist.", "", true},
+ "unknown": {"The requested URL was not found on this server.", "", true},
}
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- key := strings.TrimSuffix(path.Base(r.URL.Path), ".txt")
- res, found := cases[key]
- if found {
- http.Error(w, res.Content, res.Status)
- } else {
- http.Error(w, "Unknown test case key!", http.StatusNotFound)
- }
- }))
- defer server.Close()
-
- kubeReleaseBucketURL = server.URL
for k, v := range cases {
t.Run(k, func(t *testing.T) {
- ver, err := KubernetesReleaseVersion(k)
+
+ fileFetcher := func(url string, timeout time.Duration) (string, error) {
+ key := strings.TrimSuffix(path.Base(url), ".txt")
+ res, found := cases[key]
+ if found {
+ if v.ErrorExpected {
+ return "error", errors.New("expected error")
+ }
+ return res.Content, nil
+ }
+ return "Unknown test case key!", errors.New("unknown test case key")
+ }
+
+ ver, err := kubernetesReleaseVersion(k, fileFetcher)
t.Logf("Key: %q. Result: %q, Error: %v", k, ver, err)
switch {
case err != nil && !v.ErrorExpected:
@@ -272,7 +272,15 @@ func TestCIBuildVersion(t *testing.T) {
for _, tc := range cases {
t.Run(fmt.Sprintf("input:%s/expected:%s", tc.input, tc.expected), func(t *testing.T) {
- ver, err := KubernetesReleaseVersion(tc.input)
+
+ fileFetcher := func(url string, timeout time.Duration) (string, error) {
+ if tc.valid {
+ return tc.expected, nil
+ }
+ return "Unknown test case key!", errors.New("unknown test case key")
+ }
+
+ ver, err := kubernetesReleaseVersion(tc.input, fileFetcher)
t.Logf("Input: %q. Result: %q, Error: %v", tc.input, ver, err)
switch {
case err != nil && tc.valid:
@@ -469,3 +477,7 @@ func TestValidateStableVersion(t *testing.T) {
})
}
}
+
+func errorFetcher(url string, timeout time.Duration) (string, error) {
+ return "should not make internet calls", fmt.Errorf("should not make internet calls, tried to request url: %s", url)
+}