vendor update for E2E framework

Signed-off-by: Madhu Rajanna <madhupr007@gmail.com>
This commit is contained in:
Madhu Rajanna
2019-05-31 15:15:11 +05:30
parent 9bb23e4e32
commit d300da19b7
2149 changed files with 598692 additions and 14107 deletions

View File

@ -0,0 +1,113 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"sort"
"strings"
"github.com/pkg/errors"
)
// BuildArgumentListFromMap takes two string-string maps, one with the base arguments and one
// with optional override arguments. In the return list override arguments will precede base
// arguments
func BuildArgumentListFromMap(baseArguments map[string]string, overrideArguments map[string]string) []string {
var command []string
var keys []string
argsMap := make(map[string]string)
for k, v := range baseArguments {
argsMap[k] = v
}
for k, v := range overrideArguments {
argsMap[k] = v
}
for k := range argsMap {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
command = append(command, fmt.Sprintf("--%s=%s", k, argsMap[k]))
}
return command
}
// ParseArgumentListToMap parses a CLI argument list in the form "--foo=bar" to a string-string map
func ParseArgumentListToMap(arguments []string) map[string]string {
resultingMap := map[string]string{}
for i, arg := range arguments {
key, val, err := parseArgument(arg)
// Ignore if the first argument doesn't satisfy the criteria, it's most often the binary name
// Warn in all other cases, but don't error out. This can happen only if the user has edited the argument list by hand, so they might know what they are doing
if err != nil {
if i != 0 {
fmt.Printf("[kubeadm] WARNING: The component argument %q could not be parsed correctly. The argument must be of the form %q. Skipping...", arg, "--")
}
continue
}
resultingMap[key] = val
}
return resultingMap
}
// ReplaceArgument gets a command list; converts it to a map for easier modification, runs the provided function that
// returns a new modified map, and then converts the map back to a command string slice
func ReplaceArgument(command []string, argMutateFunc func(map[string]string) map[string]string) []string {
argMap := ParseArgumentListToMap(command)
// Save the first command (the executable) if we're sure it's not an argument (i.e. no --)
var newCommand []string
if len(command) > 0 && !strings.HasPrefix(command[0], "--") {
newCommand = append(newCommand, command[0])
}
newArgMap := argMutateFunc(argMap)
newCommand = append(newCommand, BuildArgumentListFromMap(newArgMap, map[string]string{})...)
return newCommand
}
// parseArgument parses the argument "--foo=bar" to "foo" and "bar"
func parseArgument(arg string) (string, string, error) {
if !strings.HasPrefix(arg, "--") {
return "", "", errors.New("the argument should start with '--'")
}
if !strings.Contains(arg, "=") {
return "", "", errors.New("the argument should have a '=' between the flag and the value")
}
// Remove the starting --
arg = strings.TrimPrefix(arg, "--")
// Split the string on =. Return only two substrings, since we want only key/value, but the value can include '=' as well
keyvalSlice := strings.SplitN(arg, "=", 2)
// Make sure both a key and value is present
if len(keyvalSlice) != 2 {
return "", "", errors.New("the argument must have both a key and a value")
}
if len(keyvalSlice[0]) == 0 {
return "", "", errors.New("the argument must have a key")
}
return keyvalSlice[0], keyvalSlice[1], nil
}

View File

@ -0,0 +1,83 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"strings"
"github.com/pkg/errors"
utilsexec "k8s.io/utils/exec"
)
const (
// CgroupDriverSystemd holds the systemd driver type
CgroupDriverSystemd = "systemd"
// CgroupDriverCgroupfs holds the cgroupfs driver type
CgroupDriverCgroupfs = "cgroupfs"
)
// TODO: add support for detecting the cgroup driver for CRI other than
// Docker. Currently only Docker driver detection is supported:
// Discussion:
// https://github.com/kubernetes/kubeadm/issues/844
// GetCgroupDriverDocker runs 'docker info' to obtain the docker cgroup driver
func GetCgroupDriverDocker(execer utilsexec.Interface) (string, error) {
info, err := callDockerInfo(execer)
if err != nil {
return "", err
}
return getCgroupDriverFromDockerInfo(info)
}
func validateCgroupDriver(driver string) error {
if driver != CgroupDriverCgroupfs && driver != CgroupDriverSystemd {
return errors.Errorf("unknown cgroup driver %q", driver)
}
return nil
}
// TODO: Docker 1.13 has a new way to obatain the cgroup driver:
// docker info -f "{{.CgroupDriver}}
// If the minimum supported Docker version in K8s becomes 1.13, move to
// this syntax.
func callDockerInfo(execer utilsexec.Interface) (string, error) {
out, err := execer.Command("docker", "info").Output()
if err != nil {
return "", errors.Wrap(err, "cannot execute 'docker info'")
}
return string(out), nil
}
func getCgroupDriverFromDockerInfo(info string) (string, error) {
lineSeparator := ": "
prefix := "Cgroup Driver"
for _, line := range strings.Split(info, "\n") {
if !strings.Contains(line, prefix+lineSeparator) {
continue
}
lineSplit := strings.Split(line, lineSeparator)
// At this point len(lineSplit) is always >= 2
driver := lineSplit[1]
if err := validateCgroupDriver(driver); err != nil {
return "", err
}
return driver, nil
}
return "", errors.New("cgroup driver is not defined in 'docker info'")
}

View File

@ -0,0 +1,40 @@
// +build !windows
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"github.com/pkg/errors"
"os"
"path/filepath"
"syscall"
)
// Chroot chroot()s to the new path.
// NB: All file paths after this call are effectively relative to
// `rootfs`
func Chroot(rootfs string) error {
if err := syscall.Chroot(rootfs); err != nil {
return errors.Wrapf(err, "unable to chroot to %s", rootfs)
}
root := filepath.FromSlash("/")
if err := os.Chdir(root); err != nil {
return errors.Wrapf(err, "unable to chdir to %s", root)
}
return nil
}

View File

@ -0,0 +1,30 @@
// +build windows
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"github.com/pkg/errors"
)
// Chroot chroot()s to the new path.
// NB: All file paths after this call are effectively relative to
// `rootfs`
func Chroot(rootfs string) error {
return errors.New("chroot is not implemented on Windows")
}

31
vendor/k8s.io/kubernetes/cmd/kubeadm/app/util/copy.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"os/exec"
)
// CopyDir copies the content of a folder
func CopyDir(src string, dst string) error {
cmd := exec.Command("cp", "-r", src, dst)
err := cmd.Run()
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,124 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"net"
"net/url"
"strconv"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/validation"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
// GetControlPlaneEndpoint returns a properly formatted endpoint for the control plane built according following rules:
// - If the controlPlaneEndpoint is defined, use it.
// - if the controlPlaneEndpoint is defined but without a port number, use the controlPlaneEndpoint + localEndpoint.BindPort is used.
// - Otherwise, in case the controlPlaneEndpoint is not defined, use the localEndpoint.AdvertiseAddress + the localEndpoint.BindPort.
func GetControlPlaneEndpoint(controlPlaneEndpoint string, localEndpoint *kubeadmapi.APIEndpoint) (string, error) {
// parse the bind port
bindPortString := strconv.Itoa(int(localEndpoint.BindPort))
if _, err := ParsePort(bindPortString); err != nil {
return "", errors.Wrapf(err, "invalid value %q given for api.bindPort", localEndpoint.BindPort)
}
// parse the AdvertiseAddress
var ip = net.ParseIP(localEndpoint.AdvertiseAddress)
if ip == nil {
return "", errors.Errorf("invalid value `%s` given for api.advertiseAddress", localEndpoint.AdvertiseAddress)
}
// set the control-plane url using localEndpoint.AdvertiseAddress + the localEndpoint.BindPort
controlPlaneURL := &url.URL{
Scheme: "https",
Host: net.JoinHostPort(ip.String(), bindPortString),
}
// if the controlplane endpoint is defined
if len(controlPlaneEndpoint) > 0 {
// parse the controlplane endpoint
var host, port string
var err error
if host, port, err = ParseHostPort(controlPlaneEndpoint); err != nil {
return "", errors.Wrapf(err, "invalid value %q given for controlPlaneEndpoint", controlPlaneEndpoint)
}
// if a port is provided within the controlPlaneAddress warn the users we are using it, else use the bindport
if port != "" {
if port != bindPortString {
fmt.Println("[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address")
}
} else {
port = bindPortString
}
// overrides the control-plane url using the controlPlaneAddress (and eventually the bindport)
controlPlaneURL = &url.URL{
Scheme: "https",
Host: net.JoinHostPort(host, port),
}
}
return controlPlaneURL.String(), nil
}
// ParseHostPort parses a network address of the form "host:port", "ipv4:port", "[ipv6]:port" into host and port;
// ":port" can be eventually omitted.
// If the string is not a valid representation of network address, ParseHostPort returns an error.
func ParseHostPort(hostport string) (string, string, error) {
var host, port string
var err error
// try to split host and port
if host, port, err = net.SplitHostPort(hostport); err != nil {
// if SplitHostPort returns an error, the entire hostport is considered as host
host = hostport
}
// if port is defined, parse and validate it
if port != "" {
if _, err := ParsePort(port); err != nil {
return "", "", errors.Errorf("hostport %s: port %s must be a valid number between 1 and 65535, inclusive", hostport, port)
}
}
// if host is a valid IP, returns it
if ip := net.ParseIP(host); ip != nil {
return host, port, nil
}
// if host is a validate RFC-1123 subdomain, returns it
if errs := validation.IsDNS1123Subdomain(host); len(errs) == 0 {
return host, port, nil
}
return "", "", errors.Errorf("hostport %s: host '%s' must be a valid IP address or a valid RFC-1123 DNS subdomain", hostport, host)
}
// ParsePort parses a string representing a TCP port.
// If the string is not a valid representation of a TCP port, ParsePort returns an error.
func ParsePort(port string) (int, error) {
portInt, err := strconv.Atoi(port)
if err == nil && (1 <= portInt && portInt <= 65535) {
return portInt, nil
}
return 0, errors.New("port must be a valid number between 1 and 65535, inclusive")
}

87
vendor/k8s.io/kubernetes/cmd/kubeadm/app/util/error.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"os"
"strings"
errorsutil "k8s.io/apimachinery/pkg/util/errors"
)
const (
// DefaultErrorExitCode defines exit the code for failed action generally
DefaultErrorExitCode = 1
// PreFlightExitCode defines exit the code for preflight checks
PreFlightExitCode = 2
// ValidationExitCode defines the exit code validation checks
ValidationExitCode = 3
)
// fatal prints the message if set and then exits.
func fatal(msg string, code int) {
if len(msg) > 0 {
// add newline if needed
if !strings.HasSuffix(msg, "\n") {
msg += "\n"
}
fmt.Fprint(os.Stderr, msg)
}
os.Exit(code)
}
// CheckErr prints a user friendly error to STDERR and exits with a non-zero
// exit code. Unrecognized errors will be printed with an "error: " prefix.
//
// This method is generic to the command in use and may be used by non-Kubectl
// commands.
func CheckErr(err error) {
checkErr(err, fatal)
}
// preflightError allows us to know if the error is a preflight error or not
// defining the interface here avoids an import cycle of pulling in preflight into the util package
type preflightError interface {
Preflight() bool
}
// checkErr formats a given error as a string and calls the passed handleErr
// func with that string and an exit code.
func checkErr(err error, handleErr func(string, int)) {
switch err.(type) {
case nil:
return
case preflightError:
handleErr(err.Error(), PreFlightExitCode)
case errorsutil.Aggregate:
handleErr(err.Error(), ValidationExitCode)
default:
handleErr(err.Error(), DefaultErrorExitCode)
}
}
// FormatErrMsg returns a human-readable string describing the slice of errors passed to the function
func FormatErrMsg(errs []error) string {
var errMsg string
for _, err := range errs {
errMsg = fmt.Sprintf("%s\t- %s\n", errMsg, err.Error())
}
return errMsg
}

View File

@ -0,0 +1,163 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"bufio"
"bytes"
"io"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
errorsutil "k8s.io/apimachinery/pkg/util/errors"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
clientsetscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
// MarshalToYaml marshals an object into yaml.
func MarshalToYaml(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
return MarshalToYamlForCodecs(obj, gv, clientsetscheme.Codecs)
}
// MarshalToYamlForCodecs marshals an object into yaml using the specified codec
// TODO: Is specifying the gv really needed here?
// TODO: Can we support json out of the box easily here?
func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs serializer.CodecFactory) ([]byte, error) {
mediaType := "application/yaml"
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
if !ok {
return []byte{}, errors.Errorf("unsupported media type %q", mediaType)
}
encoder := codecs.EncoderForVersion(info.Serializer, gv)
return runtime.Encode(encoder, obj)
}
// UnmarshalFromYaml unmarshals yaml into an object.
func UnmarshalFromYaml(buffer []byte, gv schema.GroupVersion) (runtime.Object, error) {
return UnmarshalFromYamlForCodecs(buffer, gv, clientsetscheme.Codecs)
}
// UnmarshalFromYamlForCodecs unmarshals yaml into an object using the specified codec
// TODO: Is specifying the gv really needed here?
// TODO: Can we support json out of the box easily here?
func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs serializer.CodecFactory) (runtime.Object, error) {
mediaType := "application/yaml"
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
if !ok {
return nil, errors.Errorf("unsupported media type %q", mediaType)
}
decoder := codecs.DecoderToVersion(info.Serializer, gv)
return runtime.Decode(decoder, buffer)
}
// SplitYAMLDocuments reads the YAML bytes per-document, unmarshals the TypeMeta information from each document
// and returns a map between the GroupVersionKind of the document and the document bytes
func SplitYAMLDocuments(yamlBytes []byte) (map[schema.GroupVersionKind][]byte, error) {
gvkmap := map[schema.GroupVersionKind][]byte{}
knownKinds := map[string]bool{}
errs := []error{}
buf := bytes.NewBuffer(yamlBytes)
reader := utilyaml.NewYAMLReader(bufio.NewReader(buf))
for {
typeMetaInfo := runtime.TypeMeta{}
// Read one YAML document at a time, until io.EOF is returned
b, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if len(b) == 0 {
break
}
// Deserialize the TypeMeta information of this byte slice
if err := yaml.Unmarshal(b, &typeMetaInfo); err != nil {
return nil, err
}
// Require TypeMeta information to be present
if len(typeMetaInfo.APIVersion) == 0 || len(typeMetaInfo.Kind) == 0 {
errs = append(errs, errors.New("invalid configuration: kind and apiVersion is mandatory information that needs to be specified in all YAML documents"))
continue
}
// Check whether the kind has been registered before. If it has, throw an error
if known := knownKinds[typeMetaInfo.Kind]; known {
errs = append(errs, errors.Errorf("invalid configuration: kind %q is specified twice in YAML file", typeMetaInfo.Kind))
continue
}
knownKinds[typeMetaInfo.Kind] = true
// Build a GroupVersionKind object from the deserialized TypeMeta object
gv, err := schema.ParseGroupVersion(typeMetaInfo.APIVersion)
if err != nil {
errs = append(errs, errors.Wrap(err, "unable to parse apiVersion"))
continue
}
gvk := gv.WithKind(typeMetaInfo.Kind)
// Save the mapping between the gvk and the bytes that object consists of
gvkmap[gvk] = b
}
if err := errorsutil.NewAggregate(errs); err != nil {
return nil, err
}
return gvkmap, nil
}
// GroupVersionKindsFromBytes parses the bytes and returns a gvk slice
func GroupVersionKindsFromBytes(b []byte) ([]schema.GroupVersionKind, error) {
gvkmap, err := SplitYAMLDocuments(b)
if err != nil {
return nil, err
}
gvks := []schema.GroupVersionKind{}
for gvk := range gvkmap {
gvks = append(gvks, gvk)
}
return gvks, nil
}
// GroupVersionKindsHasKind returns whether the following gvk slice contains the kind given as a parameter
func GroupVersionKindsHasKind(gvks []schema.GroupVersionKind, kind string) bool {
for _, gvk := range gvks {
if gvk.Kind == kind {
return true
}
}
return false
}
// GroupVersionKindsHasClusterConfiguration returns whether the following gvk slice contains a ClusterConfiguration object
func GroupVersionKindsHasClusterConfiguration(gvks ...schema.GroupVersionKind) bool {
return GroupVersionKindsHasKind(gvks, constants.ClusterConfigurationKind)
}
// GroupVersionKindsHasInitConfiguration returns whether the following gvk slice contains a InitConfiguration object
func GroupVersionKindsHasInitConfiguration(gvks ...schema.GroupVersionKind) bool {
return GroupVersionKindsHasKind(gvks, constants.InitConfigurationKind)
}
// GroupVersionKindsHasJoinConfiguration returns whether the following gvk slice contains a JoinConfiguration object
func GroupVersionKindsHasJoinConfiguration(gvks ...schema.GroupVersionKind) bool {
return GroupVersionKindsHasKind(gvks, constants.JoinConfigurationKind)
}

View File

@ -0,0 +1,38 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"bytes"
"text/template"
"github.com/pkg/errors"
)
// ParseTemplate validates and parses passed as argument template
func ParseTemplate(strtmpl string, obj interface{}) ([]byte, error) {
var buf bytes.Buffer
tmpl, err := template.New("template").Parse(strtmpl)
if err != nil {
return nil, errors.Wrap(err, "error when parsing template")
}
err = tmpl.Execute(&buf, obj)
if err != nil {
return nil, errors.Wrap(err, "error when executing template")
}
return buf.Bytes(), nil
}

View File

@ -0,0 +1,244 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
"time"
"github.com/pkg/errors"
netutil "k8s.io/apimachinery/pkg/util/net"
versionutil "k8s.io/apimachinery/pkg/util/version"
"k8s.io/klog"
pkgversion "k8s.io/kubernetes/pkg/version"
)
const (
getReleaseVersionTimeout = time.Duration(10 * time.Second)
)
var (
kubeReleaseBucketURL = "https://dl.k8s.io"
kubeReleaseRegex = regexp.MustCompile(`^v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)([-0-9a-zA-Z_\.+]*)?$`)
kubeReleaseLabelRegex = regexp.MustCompile(`^[[:lower:]]+(-[-\w_\.]+)?$`)
kubeBucketPrefixes = regexp.MustCompile(`^((release|ci|ci-cross)/)?([-\w_\.+]+)$`)
)
// KubernetesReleaseVersion is helper function that can fetch
// available version information from release servers based on
// label names, like "stable" or "latest".
//
// If argument is already semantic version string, it
// will return same string.
//
// In case of labels, it tries to fetch from release
// servers and then return actual semantic version.
//
// Available names on release servers:
// stable (latest stable release)
// stable-1 (latest stable release in 1.x)
// stable-1.0 (and similarly 1.1, 1.2, 1.3, ...)
// latest (latest release, including alpha/beta)
// 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) {
ver := normalizedBuildVersion(version)
if len(ver) != 0 {
return ver, nil
}
bucketURL, versionLabel, err := splitVersion(version)
if err != nil {
return "", err
}
// revalidate, if exact build from e.g. CI bucket requested.
ver = normalizedBuildVersion(versionLabel)
if len(ver) != 0 {
return ver, nil
}
// kubeReleaseLabelRegex matches labels such as: latest, latest-1, latest-1.10
if kubeReleaseLabelRegex.MatchString(versionLabel) {
// Try to obtain a client version.
// pkgversion.Get().String() should always return a correct version added by the golang
// linker and the build system. The version can still be missing when doing unit tests
// on individual packages.
clientVersion, clientVersionErr := kubeadmVersion(pkgversion.Get().String())
// Fetch version from the internet.
url := fmt.Sprintf("%s/%s.txt", bucketURL, versionLabel)
body, err := fetchFromURL(url, getReleaseVersionTimeout)
if err != nil {
// If the network operaton was successful but the server did not reply with StatusOK
if body != "" {
return "", err
}
// Handle air-gapped environments by falling back to the client version.
klog.Infof("could not fetch a Kubernetes version from the internet: %v", err)
klog.Infof("falling back to the local client version: %s", clientVersion)
return KubernetesReleaseVersion(clientVersion)
}
if clientVersionErr != nil {
klog.Warningf("could not obtain client version; using remote version: %s", body)
return KubernetesReleaseVersion(body)
}
// both the client and the remote version are obtained; validate them and pick a stable version
body, err = validateStableVersion(body, clientVersion)
if err != nil {
return "", err
}
// Re-validate received version and return.
return KubernetesReleaseVersion(body)
}
return "", errors.Errorf("version %q doesn't match patterns for neither semantic version nor labels (stable, latest, ...)", version)
}
// KubernetesVersionToImageTag is helper function that replaces all
// non-allowed symbols in tag strings with underscores.
// Image tag can only contain lowercase and uppercase letters, digits,
// underscores, periods and dashes.
// Current usage is for CI images where all of symbols except '+' are valid,
// but function is for generic usage where input can't be always pre-validated.
func KubernetesVersionToImageTag(version string) string {
allowed := regexp.MustCompile(`[^-a-zA-Z0-9_\.]`)
return allowed.ReplaceAllString(version, "_")
}
// KubernetesIsCIVersion checks if user requested CI version
func KubernetesIsCIVersion(version string) bool {
subs := kubeBucketPrefixes.FindAllStringSubmatch(version, 1)
if len(subs) == 1 && len(subs[0]) == 4 && strings.HasPrefix(subs[0][2], "ci") {
return true
}
return false
}
// Internal helper: returns normalized build version (with "v" prefix if needed)
// If input doesn't match known version pattern, returns empty string.
func normalizedBuildVersion(version string) string {
if kubeReleaseRegex.MatchString(version) {
if strings.HasPrefix(version, "v") {
return version
}
return "v" + version
}
return ""
}
// Internal helper: split version parts,
// Return base URL and cleaned-up version
func splitVersion(version string) (string, string, error) {
var urlSuffix string
subs := kubeBucketPrefixes.FindAllStringSubmatch(version, 1)
if len(subs) != 1 || len(subs[0]) != 4 {
return "", "", errors.Errorf("invalid version %q", version)
}
switch {
case strings.HasPrefix(subs[0][2], "ci"):
// Just use whichever the user specified
urlSuffix = subs[0][2]
default:
urlSuffix = "release"
}
url := fmt.Sprintf("%s/%s", kubeReleaseBucketURL, urlSuffix)
return url, subs[0][3], nil
}
// Internal helper: return content of URL
func fetchFromURL(url string, timeout time.Duration) (string, error) {
klog.V(2).Infof("fetching Kubernetes version from URL: %s", url)
client := &http.Client{Timeout: timeout, Transport: netutil.SetOldTransportDefaults(&http.Transport{})}
resp, err := client.Get(url)
if err != nil {
return "", errors.Errorf("unable to get URL %q: %s", url, err.Error())
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", errors.Errorf("unable to read content of URL %q: %s", url, err.Error())
}
bodyString := strings.TrimSpace(string(body))
if resp.StatusCode != http.StatusOK {
msg := fmt.Sprintf("unable to fetch file. URL: %q, status: %v", url, resp.Status)
return bodyString, errors.New(msg)
}
return bodyString, nil
}
// kubeadmVersion returns the version of the client without metadata.
func kubeadmVersion(info string) (string, error) {
v, err := versionutil.ParseSemantic(info)
if err != nil {
return "", errors.Wrap(err, "kubeadm version error")
}
// There is no utility in versionutil to get the version without the metadata,
// so this needs some manual formatting.
// Discard offsets after a release label and keep the labels down to e.g. `alpha.0` instead of
// including the offset e.g. `alpha.0.206`. This is done to comply with GCR image tags.
pre := v.PreRelease()
patch := v.Patch()
if len(pre) > 0 {
if patch > 0 {
// If the patch version is more than zero, decrement it and remove the label.
// this is done to comply with the latest stable patch release.
patch = patch - 1
pre = ""
} else {
split := strings.Split(pre, ".")
if len(split) > 2 {
pre = split[0] + "." + split[1] // Exclude the third element
} else if len(split) < 2 {
pre = split[0] + ".0" // Append .0 to a partial label
}
pre = "-" + pre
}
}
vStr := fmt.Sprintf("v%d.%d.%d%s", v.Major(), v.Minor(), patch, pre)
return vStr, nil
}
// Validate if the remote version is one Minor release newer than the client version.
// This is done to conform with "stable-X" and only allow remote versions from
// the same Patch level release.
func validateStableVersion(remoteVersion, clientVersion string) (string, error) {
verRemote, err := versionutil.ParseGeneric(remoteVersion)
if err != nil {
return "", errors.Wrap(err, "remote version error")
}
verClient, err := versionutil.ParseGeneric(clientVersion)
if err != nil {
return "", errors.Wrap(err, "client version error")
}
// If the remote Major version is bigger or if the Major versions are the same,
// but the remote Minor is bigger use the client version release. This handles Major bumps too.
if verClient.Major() < verRemote.Major() ||
(verClient.Major() == verRemote.Major()) && verClient.Minor() < verRemote.Minor() {
estimatedRelease := fmt.Sprintf("stable-%d.%d", verClient.Major(), verClient.Minor())
klog.Infof("remote version is much newer: %s; falling back to: %s", remoteVersion, estimatedRelease)
return estimatedRelease, nil
}
return remoteVersion, nil
}