diff --git a/cmd/krel/cmd/gcbmgr.go b/cmd/krel/cmd/gcbmgr.go index ad9734dc77b..08b2a82b95a 100644 --- a/cmd/krel/cmd/gcbmgr.go +++ b/cmd/krel/cmd/gcbmgr.go @@ -109,7 +109,7 @@ func init() { gcbmgrCmd.PersistentFlags().StringVar( &gcbmgrOpts.buildVersion, "build-version", - "FAKE+BUILD+POINT", + "", "Build version", ) @@ -260,18 +260,14 @@ func runGcbmgr() error { func setGCBSubstitutions(o *gcbmgrOptions) (map[string]string, error) { gcbSubs := map[string]string{} - releaseToolRepo := os.Getenv("RELEASE_TOOL_REPO") - if releaseToolRepo == "" { - releaseToolRepo = release.DefaultReleaseToolRepo - } + toolOrg := release.GetToolOrg() + gcbSubs["TOOL_ORG"] = toolOrg - releaseToolBranch := os.Getenv("RELEASE_TOOL_BRANCH") - if releaseToolBranch == "" { - releaseToolBranch = release.DefaultReleaseToolBranch - } + toolRepo := release.GetToolRepo() + gcbSubs["TOOL_REPO"] = toolRepo - gcbSubs["RELEASE_TOOL_REPO"] = releaseToolRepo - gcbSubs["RELEASE_TOOL_BRANCH"] = releaseToolBranch + toolBranch := release.GetToolBranch() + gcbSubs["TOOL_BRANCH"] = toolBranch gcpUser := o.gcpUser if gcpUser == "" { @@ -312,25 +308,33 @@ func setGCBSubstitutions(o *gcbmgrOptions) (map[string]string, error) { gcbSubs["RC"] = "" } + branch := o.branch + if branch == "" { + return gcbSubs, errors.New("Release branch must be set to continue") + } + + gcbSubs["RELEASE_BRANCH"] = branch + // TODO: Remove once we remove support for --built-at-head. gcbSubs["BUILD_AT_HEAD"] = "" - buildpoint := o.buildVersion + buildVersion := o.buildVersion + if buildVersion == "" { + var versionErr error + buildVersion, versionErr = release.GetCIKubeVersion(o.branch, false) + if versionErr != nil { + return gcbSubs, versionErr + } + } + + buildpoint := buildVersion buildpoint = strings.ReplaceAll(buildpoint, "+", "-") gcbSubs["BUILD_POINT"] = buildpoint // TODO: Add conditionals for find_green_build - buildVersion := o.buildVersion buildVersion = fmt.Sprintf("--buildversion=%s", buildVersion) gcbSubs["BUILDVERSION"] = buildVersion - branch := o.branch - if branch == "" { - return gcbSubs, errors.New("Release branch must be set to continue") - } - - gcbSubs["RELEASE_BRANCH"] = branch - kubecrossBranches := []string{ branch, "master", diff --git a/cmd/krel/cmd/gcbmgr_test.go b/cmd/krel/cmd/gcbmgr_test.go index c32f238c9af..8f36ce6f19d 100644 --- a/cmd/krel/cmd/gcbmgr_test.go +++ b/cmd/krel/cmd/gcbmgr_test.go @@ -18,6 +18,7 @@ package cmd import ( "fmt" + "os" "testing" "github.com/stretchr/testify/assert" @@ -96,6 +97,9 @@ func TestSetGCBSubstitutionsSuccess(t *testing.T) { testcases := []struct { name string gcbmgrOpts gcbmgrOptions + toolOrg string + toolRepo string + toolBranch string expected map[string]string }{ { @@ -106,16 +110,15 @@ func TestSetGCBSubstitutionsSuccess(t *testing.T) { gcpUser: "test-user", }, expected: map[string]string{ - "BUILDVERSION": "--buildversion=", - "BUILD_AT_HEAD": "", - "BUILD_POINT": "", - "OFFICIAL": "", - "OFFICIAL_TAG": "", - "RC": "", - "RC_TAG": "", - "RELEASE_BRANCH": "master", - "RELEASE_TOOL_BRANCH": "master", - "RELEASE_TOOL_REPO": "https://github.com/kubernetes/release", + "BUILD_AT_HEAD": "", + "OFFICIAL": "", + "OFFICIAL_TAG": "", + "RC": "", + "RC_TAG": "", + "RELEASE_BRANCH": "master", + "TOOL_ORG": "kubernetes", + "TOOL_REPO": "release", + "TOOL_BRANCH": "master", }, }, { @@ -126,16 +129,15 @@ func TestSetGCBSubstitutionsSuccess(t *testing.T) { gcpUser: "test-user", }, expected: map[string]string{ - "BUILDVERSION": "--buildversion=", - "BUILD_AT_HEAD": "", - "BUILD_POINT": "", - "OFFICIAL": "", - "OFFICIAL_TAG": "", - "RC": "--rc", - "RC_TAG": "rc", - "RELEASE_BRANCH": "release-1.14", - "RELEASE_TOOL_BRANCH": "master", - "RELEASE_TOOL_REPO": "https://github.com/kubernetes/release", + "BUILD_AT_HEAD": "", + "OFFICIAL": "", + "OFFICIAL_TAG": "", + "RC": "--rc", + "RC_TAG": "rc", + "RELEASE_BRANCH": "release-1.14", + "TOOL_ORG": "kubernetes", + "TOOL_REPO": "release", + "TOOL_BRANCH": "master", }, }, { @@ -146,16 +148,37 @@ func TestSetGCBSubstitutionsSuccess(t *testing.T) { gcpUser: "test-user", }, expected: map[string]string{ - "BUILDVERSION": "--buildversion=", - "BUILD_AT_HEAD": "", - "BUILD_POINT": "", - "OFFICIAL": "--official", - "OFFICIAL_TAG": "official", - "RC": "", - "RC_TAG": "", - "RELEASE_BRANCH": "release-1.15", - "RELEASE_TOOL_BRANCH": "master", - "RELEASE_TOOL_REPO": "https://github.com/kubernetes/release", + "BUILD_AT_HEAD": "", + "OFFICIAL": "--official", + "OFFICIAL_TAG": "official", + "RC": "", + "RC_TAG": "", + "RELEASE_BRANCH": "release-1.15", + "TOOL_ORG": "kubernetes", + "TOOL_REPO": "release", + "TOOL_BRANCH": "master", + }, + }, + { + name: "release-1.16 official with custom tool org, repo, and branch", + gcbmgrOpts: gcbmgrOptions{ + branch: "release-1.16", + releaseType: "official", + gcpUser: "test-user", + }, + toolOrg: "honk", + toolRepo: "best-tools", + toolBranch: "tool-branch", + expected: map[string]string{ + "BUILD_AT_HEAD": "", + "OFFICIAL": "--official", + "OFFICIAL_TAG": "official", + "RC": "", + "RC_TAG": "", + "RELEASE_BRANCH": "release-1.16", + "TOOL_ORG": "honk", + "TOOL_REPO": "best-tools", + "TOOL_BRANCH": "tool-branch", }, }, } @@ -164,6 +187,9 @@ func TestSetGCBSubstitutionsSuccess(t *testing.T) { t.Logf("Test case: %s", tc.name) opts := tc.gcbmgrOpts + os.Setenv("TOOL_ORG", tc.toolOrg) + os.Setenv("TOOL_REPO", tc.toolRepo) + os.Setenv("TOOL_BRANCH", tc.toolBranch) subs, err := setGCBSubstitutions(&opts) actual := dropDynamicSubstitutions(subs) @@ -205,7 +231,7 @@ func dropDynamicSubstitutions(orig map[string]string) (result map[string]string) result = orig for k := range result { - if k == "GCP_USER_TAG" || k == "KUBE_CROSS_VERSION" { + if k == "BUILDVERSION" || k == "BUILD_POINT" || k == "GCP_USER_TAG" || k == "KUBE_CROSS_VERSION" { delete(result, k) } } diff --git a/pkg/kubepkg/BUILD.bazel b/pkg/kubepkg/BUILD.bazel index 4915f29fda5..375f4ce97cc 100644 --- a/pkg/kubepkg/BUILD.bazel +++ b/pkg/kubepkg/BUILD.bazel @@ -10,6 +10,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/command:go_default_library", + "//pkg/release:go_default_library", "//pkg/util:go_default_library", "@com_github_blang_semver//:go_default_library", "@com_github_google_go_github_v29//github:go_default_library", @@ -36,8 +37,5 @@ go_test( name = "go_default_test", srcs = ["kubepkg_test.go"], embed = [":go_default_library"], - deps = [ - "@com_github_stretchr_testify//assert:go_default_library", - "@com_github_stretchr_testify//require:go_default_library", - ], + deps = ["@com_github_stretchr_testify//assert:go_default_library"], ) diff --git a/pkg/kubepkg/kubepkg.go b/pkg/kubepkg/kubepkg.go index 01b624d5e49..61148103752 100644 --- a/pkg/kubepkg/kubepkg.go +++ b/pkg/kubepkg/kubepkg.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "io/ioutil" - "net/http" "os" "path/filepath" "reflect" @@ -34,6 +33,7 @@ import ( "github.com/sirupsen/logrus" "k8s.io/release/pkg/command" + "k8s.io/release/pkg/release" "k8s.io/release/pkg/util" ) @@ -60,6 +60,8 @@ const ( templateRootDir = "templates" kubeadmConf = "10-kubeadm.conf" + + useSemver = true ) var ( @@ -391,46 +393,12 @@ func getKubernetesVersion(packageDef *PackageDefinition) (string, error) { } switch packageDef.Channel { case ChannelTesting: - return getTestingKubeVersion() + return release.GetStablePrereleaseKubeVersion(useSemver) case ChannelNightly: - return getNightlyKubeVersion() - } - - return getReleaseKubeVersion() -} - -func getReleaseKubeVersion() (string, error) { - logrus.Info("Retrieving Kubernetes release version...") - return fetchVersion("https://dl.k8s.io/release/stable.txt") -} - -func getTestingKubeVersion() (string, error) { - logrus.Info("Retrieving Kubernetes testing version...") - return fetchVersion("https://dl.k8s.io/release/latest.txt") -} - -func getNightlyKubeVersion() (string, error) { - logrus.Info("Retrieving Kubernetes nightly version...") - return fetchVersion("https://dl.k8s.io/ci/k8s-master.txt") -} - -func fetchVersion(url string) (string, error) { - res, err := http.Get(url) - if err != nil { - return "", err + return release.GetLatestCIKubeVersion(useSemver) } - versionBytes, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - return "", err - } - - // Remove a newline and the v prefix from the string - version := strings.Replace(strings.Replace(string(versionBytes), "v", "", 1), "\n", "", 1) - - logrus.Infof("Retrieved Kubernetes version: %s", version) - return version, nil + return release.GetStableReleaseKubeVersion(useSemver) } func getCNIVersion(packageDef *PackageDefinition) (string, error) { @@ -584,7 +552,7 @@ func getCIBuildsDownloadLinkBase(packageDef *PackageDefinition) (string, error) ciVersion := packageDef.KubernetesVersion if ciVersion == "" { var err error - ciVersion, err = getNightlyKubeVersion() + ciVersion, err = release.GetLatestCIKubeVersion(useSemver) if err != nil { return "", err } diff --git a/pkg/kubepkg/kubepkg_test.go b/pkg/kubepkg/kubepkg_test.go index 41e5f26642c..d2d7a4c0ff6 100644 --- a/pkg/kubepkg/kubepkg_test.go +++ b/pkg/kubepkg/kubepkg_test.go @@ -20,7 +20,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestGetPackageVersionSuccess(t *testing.T) { @@ -120,57 +119,6 @@ func TestGetKubernetesVersionFailure(t *testing.T) { a.Error(err) } -func TestFetchVersionSuccess(t *testing.T) { - testcases := []struct { - name string - url string - expected string - }{ - { - name: "Release URL", - url: "https://dl.k8s.io/release/stable-1.13.txt", - expected: "1.13.12", - }, - { - name: "CI URL", - url: "https://dl.k8s.io/ci/latest-1.14.txt", - expected: "1.14.11-beta.1.2+c8b135d0b49c44", - }, - } - - for _, tc := range testcases { - actual, err := fetchVersion(tc.url) - - if err != nil { - t.Fatalf("did not expect an error: %v", err) - } - - assert.Equal(t, tc.expected, actual) - } -} - -func TestFetchVersionFailure(t *testing.T) { - testcases := []struct { - name string - url string - }{ - { - name: "Empty URL string", - url: "", - }, - { - name: "Bad URL", - url: "https://fake.url", - }, - } - - for _, tc := range testcases { - _, err := fetchVersion(tc.url) - - require.Error(t, err) - } -} - func TestGetCNIVersionSuccess(t *testing.T) { testcases := []struct { name string diff --git a/pkg/release/BUILD.bazel b/pkg/release/BUILD.bazel index c913edc9ef7..83165680590 100644 --- a/pkg/release/BUILD.bazel +++ b/pkg/release/BUILD.bazel @@ -6,7 +6,9 @@ go_library( importpath = "k8s.io/release/pkg/release", visibility = ["//visibility:public"], deps = [ + "//pkg/git:go_default_library", "//pkg/util:go_default_library", + "@com_github_blang_semver//:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", ], @@ -30,5 +32,8 @@ go_test( name = "go_default_test", srcs = ["release_test.go"], embed = [":go_default_library"], - deps = ["@com_github_stretchr_testify//require:go_default_library"], + deps = [ + "@com_github_stretchr_testify//assert:go_default_library", + "@com_github_stretchr_testify//require:go_default_library", + ], ) diff --git a/pkg/release/release.go b/pkg/release/release.go index 93be8dbb9b1..3405527f09e 100644 --- a/pkg/release/release.go +++ b/pkg/release/release.go @@ -19,24 +19,28 @@ package release import ( "fmt" "io/ioutil" - "net/http" + "net/url" + "os" + "path" "path/filepath" "regexp" "strings" + "github.com/blang/semver" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "k8s.io/release/pkg/git" "k8s.io/release/pkg/util" ) const ( // gcbmgr/anago defaults - DefaultReleaseToolRepo = "https://github.com/kubernetes/release" - DefaultReleaseToolBranch = "master" - DefaultProject = "kubernetes-release-test" - DefaultDiskSize = "300" - BucketPrefix = "kubernetes-release-" + DefaultToolRepo = "release" + DefaultToolBranch = "master" + DefaultProject = "kubernetes-release-test" + DefaultDiskSize = "300" + BucketPrefix = "kubernetes-release-" versionReleaseRE = `v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[a-zA-Z0-9]+)*\.*(0|[1-9][0-9]*)?` versionBuildRE = `([0-9]{1,})\+([0-9a-f]{5,40})` @@ -71,24 +75,83 @@ const ( WindowsGCSPath = "gcs-stage/extra/gce/windows" ) +var ( + DefaultToolOrg = git.DefaultGithubOrg +) + +// GetDefaultKubernetesRepoURL returns the default HTTPS repo URL for Release Engineering tools. +// Expected: https://github.com/kubernetes/release +func GetDefaultToolRepoURL() (string, error) { + return GetToolRepoURL(DefaultToolOrg, DefaultToolRepo, false) +} + +// GetKubernetesRepoURL takes a GitHub org and repo, and useSSH as a boolean and +// returns a repo URL for Release Engineering tools. +// Expected result is one of the following: +// - https://github.com//release +// - git@github.com:/release +func GetToolRepoURL(org, repo string, useSSH bool) (string, error) { + if org == "" { + org = GetToolOrg() + } + if repo == "" { + repo = GetToolRepo() + } + + return git.GetRepoURL(org, repo, useSSH) +} + +// GetToolOrg checks if the 'TOOL_ORG' environment variable is set. +// If 'TOOL_ORG' is non-empty, it returns the value. Otherwise, it returns DefaultToolOrg. +func GetToolOrg() string { + toolOrg := os.Getenv("TOOL_ORG") + if toolOrg == "" { + toolOrg = DefaultToolOrg + } + + return toolOrg +} + +// GetToolRepo checks if the 'TOOL_REPO' environment variable is set. +// If 'TOOL_REPO' is non-empty, it returns the value. Otherwise, it returns DefaultToolRepo. +func GetToolRepo() string { + toolRepo := os.Getenv("TOOL_REPO") + if toolRepo == "" { + toolRepo = DefaultToolRepo + } + + return toolRepo +} + +// GetToolBranch checks if the 'TOOL_BRANCH' environment variable is set. +// If 'TOOL_BRANCH' is non-empty, it returns the value. Otherwise, it returns DefaultToolBranch. +func GetToolBranch() string { + toolBranch := os.Getenv("TOOL_BRANCH") + if toolBranch == "" { + toolBranch = DefaultToolBranch + } + + return toolBranch +} + // BuiltWithBazel determines whether the most recent release was built with Bazel. -func BuiltWithBazel(path, releaseKind string) (bool, error) { +func BuiltWithBazel(workDir, releaseKind string) (bool, error) { tar := releaseKind + tarballExtension - bazelBuild := filepath.Join(path, bazelBuildPath, tar) - dockerBuild := filepath.Join(path, dockerBuildPath, tar) + bazelBuild := filepath.Join(workDir, bazelBuildPath, tar) + dockerBuild := filepath.Join(workDir, dockerBuildPath, tar) return util.MoreRecent(bazelBuild, dockerBuild) } // ReadBazelVersion reads the version from a Bazel build. -func ReadBazelVersion(path string) (string, error) { - version, err := ioutil.ReadFile(filepath.Join(path, bazelVersionPath)) +func ReadBazelVersion(workDir string) (string, error) { + version, err := ioutil.ReadFile(filepath.Join(workDir, bazelVersionPath)) return string(version), err } // ReadDockerizedVersion reads the version from a Dockerized build. -func ReadDockerizedVersion(path, releaseKind string) (string, error) { +func ReadDockerizedVersion(workDir, releaseKind string) (string, error) { tar := releaseKind + tarballExtension - dockerTarball := filepath.Join(path, dockerBuildPath, tar) + dockerTarball := filepath.Join(workDir, dockerBuildPath, tar) versionFile := filepath.Join(releaseKind, dockerVersionPath) reader, err := util.ReadFileFromGzippedTar(dockerTarball, versionFile) if err != nil { @@ -108,29 +171,87 @@ func IsDirtyBuild(build string) bool { return strings.Contains(build, "dirty") } +// TODO: Consider collapsing some of these functions. +// Keeping them as-is for now as kubepkg is dependent on them. +func GetStableReleaseKubeVersion(useSemver bool) (string, error) { + logrus.Info("Retrieving Kubernetes release version...") + return GetKubeVersion("https://dl.k8s.io/release/stable.txt", useSemver) +} + +func GetStablePrereleaseKubeVersion(useSemver bool) (string, error) { + logrus.Info("Retrieving Kubernetes testing version...") + return GetKubeVersion("https://dl.k8s.io/release/latest.txt", useSemver) +} + +func GetLatestCIKubeVersion(useSemver bool) (string, error) { + logrus.Info("Retrieving Kubernetes latest build version...") + return GetKubeVersion("https://dl.k8s.io/ci/latest.txt", useSemver) +} + +func GetCIKubeVersion(branch string, useSemver bool) (string, error) { + logrus.Infof("Retrieving Kubernetes build version on the '%s' branch...", branch) + // TODO: We may need to check if the branch exists first to handle the branch cut scenario + versionMarker := "latest" + if branch != "master" { + version := strings.TrimPrefix(branch, "release-") + + versionMarker = fmt.Sprintf("%s-%s", versionMarker, version) + } + + versionMarkerFile := fmt.Sprintf("%s.txt", versionMarker) + logrus.Infof("Version marker file: %s", versionMarkerFile) + + u, parseErr := url.Parse("https://dl.k8s.io/ci") + if parseErr != nil { + return "", errors.Wrap(parseErr, "failed to parse URL base") + } + + u.Path = path.Join(u.Path, versionMarkerFile) + markerURL := u.String() + + return GetKubeVersion(markerURL, useSemver) +} + +func GetKubeVersion(markerURL string, useSemver bool) (string, error) { + logrus.Infof("Retrieving Kubernetes build version from %s...", markerURL) + version, httpErr := util.GetURLResponse(markerURL, true) + if httpErr != nil { + return "", httpErr + } + + if useSemver { + // Remove the 'v' prefix from the string to make the version SemVer compliant + version = strings.TrimPrefix(version, "v") + + sem, semverErr := semver.Parse(version) + if semverErr != nil { + return "", semverErr + } + + version = sem.String() + } + + logrus.Infof("Retrieved Kubernetes version: %s", version) + return version, nil +} + // GetKubecrossVersion returns the current kube-cross container version. // Replaces release::kubecross_version func GetKubecrossVersion(branches ...string) (string, error) { - var version string - - for _, branch := range branches { + for i, branch := range branches { logrus.Infof("Trying to get the kube-cross version for %s...", branch) versionURL := fmt.Sprintf("https://raw.githubusercontent.com/kubernetes/kubernetes/%s/build/build-image/cross/VERSION", branch) - resp, httpErr := http.Get(versionURL) + version, httpErr := util.GetURLResponse(versionURL, true) if httpErr != nil { - return "", errors.Wrapf(httpErr, "an error occurred GET-ing %s", versionURL) + if i < len(branches)-1 { + logrus.Infof("Error retrieving the kube-cross version for the '%s': %v", branch, httpErr) + } else { + return "", httpErr + } } - defer resp.Body.Close() - body, ioErr := ioutil.ReadAll(resp.Body) - if ioErr != nil { - return "", errors.Wrapf(ioErr, "could not handle the response body for %s", versionURL) - } - - version = strings.TrimSpace(string(body)) - if version != "" { logrus.Infof("Found the following kube-cross version: %s", version) return version, nil diff --git a/pkg/release/release_test.go b/pkg/release/release_test.go index df97daee409..4619a35185e 100644 --- a/pkg/release/release_test.go +++ b/pkg/release/release_test.go @@ -26,9 +26,86 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestGetDefaultToolRepoURLSuccess(t *testing.T) { + testcases := []struct { + name string + useSSH bool + expected string + }{ + { + name: "default HTTPS", + expected: "https://github.com/kubernetes/release", + }, + } + + for _, tc := range testcases { + t.Logf("Test case: %s", tc.name) + + actual, err := GetDefaultToolRepoURL() + assert.Equal(t, tc.expected, actual) + assert.Nil(t, err) + } +} + +func TestGetToolRepoURLSuccess(t *testing.T) { + testcases := []struct { + name string + org string + repo string + useSSH bool + expected string + }{ + { + name: "default HTTPS", + expected: "https://github.com/kubernetes/release", + }, + { + name: "ssh with custom org", + org: "fake-org", + useSSH: true, + expected: "git@github.com:fake-org/release", + }, + } + + for _, tc := range testcases { + t.Logf("Test case: %s", tc.name) + + actual, err := GetToolRepoURL(tc.org, tc.repo, tc.useSSH) + assert.Equal(t, tc.expected, actual) + assert.Nil(t, err) + } +} + +func TestGetToolBranchSuccess(t *testing.T) { + testcases := []struct { + name string + branch string + expected string + }{ + { + name: "default branch", + expected: "master", + }, + { + name: "custom branch", + branch: "tool-branch", + expected: "tool-branch", + }, + } + + for _, tc := range testcases { + t.Logf("Test case: %s", tc.name) + os.Setenv("TOOL_BRANCH", tc.branch) + + actual := GetToolBranch() + assert.Equal(t, tc.expected, actual) + } +} + func TestBuiltWithBazel(t *testing.T) { baseTmpDir, err := ioutil.TempDir("", "") require.Nil(t, err) @@ -324,6 +401,67 @@ func TestIsDirtyBuild(t *testing.T) { } } +func TestGetKubeVersionSuccess(t *testing.T) { + testcases := []struct { + name string + url string + expected string + useSemver bool + }{ + { + name: "Release URL (semver)", + url: "https://dl.k8s.io/release/stable-1.13.txt", + expected: "1.13.12", + useSemver: true, + }, + { + name: "CI URL (semver)", + url: "https://dl.k8s.io/ci/latest-1.14.txt", + expected: "1.14.11-beta.1.2+c8b135d0b49c44", + useSemver: true, + }, + { + name: "CI URL (non-semver)", + url: "https://dl.k8s.io/ci/latest-1.14.txt", + expected: "v1.14.11-beta.1.2+c8b135d0b49c44", + useSemver: false, + }, + } + + for _, tc := range testcases { + actual, err := GetKubeVersion(tc.url, tc.useSemver) + + if err != nil { + t.Fatalf("did not expect an error: %v", err) + } + + assert.Equal(t, tc.expected, actual) + } +} + +func TestGetKubeVersionFailure(t *testing.T) { + testcases := []struct { + name string + url string + useSemver bool + }{ + { + name: "Empty URL string", + url: "", + }, + { + name: "Bad URL", + url: "https://fake.url", + }, + } + + for _, tc := range testcases { + _, err := GetKubeVersion(tc.url, tc.useSemver) + + require.Error(t, err) + } +} + func cleanupTmps(t *testing.T, dir ...string) { for _, each := range dir { require.Nil(t, os.RemoveAll(each)) diff --git a/pkg/util/common.go b/pkg/util/common.go index 1e8d1bfd0b6..bba14da30d4 100644 --- a/pkg/util/common.go +++ b/pkg/util/common.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "os" "path/filepath" "strings" @@ -38,6 +39,32 @@ const ( TagPrefix = "v" ) +func GetURLResponse(url string, trim bool) (string, error) { + resp, httpErr := http.Get(url) + if httpErr != nil { + return "", errors.Wrapf(httpErr, "an error occurred GET-ing %s", url) + } + + defer resp.Body.Close() + statusOK := resp.StatusCode >= 200 && resp.StatusCode < 300 + if !statusOK { + errMsg := fmt.Sprintf("HTTP status not OK (%v) for %s", resp.StatusCode, url) + return "", errors.New(errMsg) + } + + respBytes, ioErr := ioutil.ReadAll(resp.Body) + if ioErr != nil { + return "", errors.Wrapf(ioErr, "could not handle the response body for %s", url) + } + + respString := string(respBytes) + if trim { + respString = strings.TrimSpace(respString) + } + + return respString, nil +} + // PackagesAvailable takes a slice of packages and determines if they are installed // on the host OS. Replaces common::check_packages. func PackagesAvailable(packages ...string) (bool, error) {