Merge pull request #49119 from kad/n-addons-repo

Automatic merge from submit-queue (batch tested with PRs 50693, 50831, 47506, 49119, 50871)

kubeadm: Implement support for using images from CI builds

**What this PR does / why we need it**: Implements support for CI images in kubeadm

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes kubernetes/kubeadm#337

**Special notes for your reviewer**:

**Release note**:
```release-note
- kubeadm now supports "ci/latest-1.8" or "ci-cross/latest-1.8" and similar labels.
```
This commit is contained in:
Kubernetes Submit Queue 2017-08-21 14:30:03 -07:00 committed by GitHub
commit f40d07480f
11 changed files with 439 additions and 19 deletions

View File

@ -40,6 +40,7 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
obj.Etcd.Image = "foo"
obj.Etcd.DataDir = "foo"
obj.ImageRepository = "foo"
obj.CIImageRepository = ""
obj.UnifiedControlPlaneImage = "foo"
obj.FeatureFlags = map[string]bool{}
},

View File

@ -49,6 +49,11 @@ type MasterConfiguration struct {
// ImageRepository what container registry to pull control plane images from
ImageRepository string
// Container registry for core images generated by CI
// +k8s:conversion-gen=false
CIImageRepository string
// UnifiedControlPlaneImage specifies if a specific container image should be used for all control plane components
UnifiedControlPlaneImage string
@ -115,3 +120,15 @@ type NodeConfiguration struct {
// the security of kubeadm since other nodes can impersonate the master.
DiscoveryTokenUnsafeSkipCAVerification bool
}
// GetControlPlaneImageRepository returns name of image repository
// for control plane images (API,Controller Manager,Scheduler and Proxy)
// It will override location with CI registry name in case user requests special
// Kubernetes version from CI build area.
// (See: kubeadmconstants.DefaultCIImageRepository)
func (cfg *MasterConfiguration) GetControlPlaneImageRepository() string {
if cfg.CIImageRepository != "" {
return cfg.CIImageRepository
}
return cfg.ImageRepository
}

View File

@ -12,10 +12,12 @@ go_library(
"doc.go",
"register.go",
"types.go",
"zz_generated.conversion.go",
"zz_generated.deepcopy.go",
"zz_generated.defaults.go",
],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",

View File

@ -17,4 +17,5 @@ limitations under the License.
// +k8s:defaulter-gen=TypeMeta
// +groupName=kubeadm.k8s.io
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm
package v1alpha1 // import "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"

View File

@ -0,0 +1,279 @@
// +build !ignore_autogenerated
/*
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.
*/
// This file was autogenerated by conversion-gen. Do not edit it manually!
package v1alpha1
import (
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
time "time"
unsafe "unsafe"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(scheme *runtime.Scheme) error {
return scheme.AddGeneratedConversionFuncs(
Convert_v1alpha1_API_To_kubeadm_API,
Convert_kubeadm_API_To_v1alpha1_API,
Convert_v1alpha1_Etcd_To_kubeadm_Etcd,
Convert_kubeadm_Etcd_To_v1alpha1_Etcd,
Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration,
Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration,
Convert_v1alpha1_Networking_To_kubeadm_Networking,
Convert_kubeadm_Networking_To_v1alpha1_Networking,
Convert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration,
Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration,
Convert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery,
Convert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery,
)
}
func autoConvert_v1alpha1_API_To_kubeadm_API(in *API, out *kubeadm.API, s conversion.Scope) error {
out.AdvertiseAddress = in.AdvertiseAddress
out.BindPort = in.BindPort
return nil
}
// Convert_v1alpha1_API_To_kubeadm_API is an autogenerated conversion function.
func Convert_v1alpha1_API_To_kubeadm_API(in *API, out *kubeadm.API, s conversion.Scope) error {
return autoConvert_v1alpha1_API_To_kubeadm_API(in, out, s)
}
func autoConvert_kubeadm_API_To_v1alpha1_API(in *kubeadm.API, out *API, s conversion.Scope) error {
out.AdvertiseAddress = in.AdvertiseAddress
out.BindPort = in.BindPort
return nil
}
// Convert_kubeadm_API_To_v1alpha1_API is an autogenerated conversion function.
func Convert_kubeadm_API_To_v1alpha1_API(in *kubeadm.API, out *API, s conversion.Scope) error {
return autoConvert_kubeadm_API_To_v1alpha1_API(in, out, s)
}
func autoConvert_v1alpha1_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s conversion.Scope) error {
out.Endpoints = *(*[]string)(unsafe.Pointer(&in.Endpoints))
out.CAFile = in.CAFile
out.CertFile = in.CertFile
out.KeyFile = in.KeyFile
out.DataDir = in.DataDir
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
out.Image = in.Image
return nil
}
// Convert_v1alpha1_Etcd_To_kubeadm_Etcd is an autogenerated conversion function.
func Convert_v1alpha1_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s conversion.Scope) error {
return autoConvert_v1alpha1_Etcd_To_kubeadm_Etcd(in, out, s)
}
func autoConvert_kubeadm_Etcd_To_v1alpha1_Etcd(in *kubeadm.Etcd, out *Etcd, s conversion.Scope) error {
if in.Endpoints == nil {
out.Endpoints = make([]string, 0)
} else {
out.Endpoints = *(*[]string)(unsafe.Pointer(&in.Endpoints))
}
out.CAFile = in.CAFile
out.CertFile = in.CertFile
out.KeyFile = in.KeyFile
out.DataDir = in.DataDir
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
out.Image = in.Image
return nil
}
// Convert_kubeadm_Etcd_To_v1alpha1_Etcd is an autogenerated conversion function.
func Convert_kubeadm_Etcd_To_v1alpha1_Etcd(in *kubeadm.Etcd, out *Etcd, s conversion.Scope) error {
return autoConvert_kubeadm_Etcd_To_v1alpha1_Etcd(in, out, s)
}
func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in *MasterConfiguration, out *kubeadm.MasterConfiguration, s conversion.Scope) error {
if err := Convert_v1alpha1_API_To_kubeadm_API(&in.API, &out.API, s); err != nil {
return err
}
if err := Convert_v1alpha1_Etcd_To_kubeadm_Etcd(&in.Etcd, &out.Etcd, s); err != nil {
return err
}
if err := Convert_v1alpha1_Networking_To_kubeadm_Networking(&in.Networking, &out.Networking, s); err != nil {
return err
}
out.KubernetesVersion = in.KubernetesVersion
out.CloudProvider = in.CloudProvider
out.NodeName = in.NodeName
out.AuthorizationModes = *(*[]string)(unsafe.Pointer(&in.AuthorizationModes))
out.Token = in.Token
out.TokenTTL = time.Duration(in.TokenTTL)
out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs))
out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs))
out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs))
out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs))
out.CertificatesDir = in.CertificatesDir
out.ImageRepository = in.ImageRepository
out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage
out.FeatureFlags = *(*map[string]bool)(unsafe.Pointer(&in.FeatureFlags))
return nil
}
// Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration is an autogenerated conversion function.
func Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in *MasterConfiguration, out *kubeadm.MasterConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in, out, s)
}
func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in *kubeadm.MasterConfiguration, out *MasterConfiguration, s conversion.Scope) error {
if err := Convert_kubeadm_API_To_v1alpha1_API(&in.API, &out.API, s); err != nil {
return err
}
if err := Convert_kubeadm_Etcd_To_v1alpha1_Etcd(&in.Etcd, &out.Etcd, s); err != nil {
return err
}
if err := Convert_kubeadm_Networking_To_v1alpha1_Networking(&in.Networking, &out.Networking, s); err != nil {
return err
}
out.KubernetesVersion = in.KubernetesVersion
out.CloudProvider = in.CloudProvider
out.NodeName = in.NodeName
if in.AuthorizationModes == nil {
out.AuthorizationModes = make([]string, 0)
} else {
out.AuthorizationModes = *(*[]string)(unsafe.Pointer(&in.AuthorizationModes))
}
out.Token = in.Token
out.TokenTTL = time.Duration(in.TokenTTL)
out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs))
out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs))
out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs))
if in.APIServerCertSANs == nil {
out.APIServerCertSANs = make([]string, 0)
} else {
out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs))
}
out.CertificatesDir = in.CertificatesDir
out.ImageRepository = in.ImageRepository
// INFO: in.CIImageRepository opted out of conversion generation
out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage
out.FeatureFlags = *(*map[string]bool)(unsafe.Pointer(&in.FeatureFlags))
return nil
}
// Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration is an autogenerated conversion function.
func Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in *kubeadm.MasterConfiguration, out *MasterConfiguration, s conversion.Scope) error {
return autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in, out, s)
}
func autoConvert_v1alpha1_Networking_To_kubeadm_Networking(in *Networking, out *kubeadm.Networking, s conversion.Scope) error {
out.ServiceSubnet = in.ServiceSubnet
out.PodSubnet = in.PodSubnet
out.DNSDomain = in.DNSDomain
return nil
}
// Convert_v1alpha1_Networking_To_kubeadm_Networking is an autogenerated conversion function.
func Convert_v1alpha1_Networking_To_kubeadm_Networking(in *Networking, out *kubeadm.Networking, s conversion.Scope) error {
return autoConvert_v1alpha1_Networking_To_kubeadm_Networking(in, out, s)
}
func autoConvert_kubeadm_Networking_To_v1alpha1_Networking(in *kubeadm.Networking, out *Networking, s conversion.Scope) error {
out.ServiceSubnet = in.ServiceSubnet
out.PodSubnet = in.PodSubnet
out.DNSDomain = in.DNSDomain
return nil
}
// Convert_kubeadm_Networking_To_v1alpha1_Networking is an autogenerated conversion function.
func Convert_kubeadm_Networking_To_v1alpha1_Networking(in *kubeadm.Networking, out *Networking, s conversion.Scope) error {
return autoConvert_kubeadm_Networking_To_v1alpha1_Networking(in, out, s)
}
func autoConvert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration(in *NodeConfiguration, out *kubeadm.NodeConfiguration, s conversion.Scope) error {
out.CACertPath = in.CACertPath
out.DiscoveryFile = in.DiscoveryFile
out.DiscoveryToken = in.DiscoveryToken
out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers))
out.NodeName = in.NodeName
out.TLSBootstrapToken = in.TLSBootstrapToken
out.Token = in.Token
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
return nil
}
// Convert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration is an autogenerated conversion function.
func Convert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration(in *NodeConfiguration, out *kubeadm.NodeConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration(in, out, s)
}
func autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kubeadm.NodeConfiguration, out *NodeConfiguration, s conversion.Scope) error {
out.CACertPath = in.CACertPath
out.DiscoveryFile = in.DiscoveryFile
out.DiscoveryToken = in.DiscoveryToken
if in.DiscoveryTokenAPIServers == nil {
out.DiscoveryTokenAPIServers = make([]string, 0)
} else {
out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers))
}
out.NodeName = in.NodeName
out.TLSBootstrapToken = in.TLSBootstrapToken
out.Token = in.Token
if in.DiscoveryTokenCACertHashes == nil {
out.DiscoveryTokenCACertHashes = make([]string, 0)
} else {
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
}
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
return nil
}
// Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration is an autogenerated conversion function.
func Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kubeadm.NodeConfiguration, out *NodeConfiguration, s conversion.Scope) error {
return autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in, out, s)
}
func autoConvert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery(in *TokenDiscovery, out *kubeadm.TokenDiscovery, s conversion.Scope) error {
out.ID = in.ID
out.Secret = in.Secret
out.Addresses = *(*[]string)(unsafe.Pointer(&in.Addresses))
return nil
}
// Convert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery is an autogenerated conversion function.
func Convert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery(in *TokenDiscovery, out *kubeadm.TokenDiscovery, s conversion.Scope) error {
return autoConvert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery(in, out, s)
}
func autoConvert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery(in *kubeadm.TokenDiscovery, out *TokenDiscovery, s conversion.Scope) error {
out.ID = in.ID
out.Secret = in.Secret
if in.Addresses == nil {
out.Addresses = make([]string, 0)
} else {
out.Addresses = *(*[]string)(unsafe.Pointer(&in.Addresses))
}
return nil
}
// Convert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery is an autogenerated conversion function.
func Convert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery(in *kubeadm.TokenDiscovery, out *TokenDiscovery, s conversion.Scope) error {
return autoConvert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery(in, out, s)
}

View File

@ -116,6 +116,9 @@ const (
// NodeBootstrapTokenAuthGroup specifies which group a Node Bootstrap Token should be authenticated in
// TODO: This should be changed in the v1.8 dev cycle to a node-BT-specific group instead of the generic Bootstrap Token group that is used now
NodeBootstrapTokenAuthGroup = "system:bootstrappers"
// DefaultCIImageRepository points to image registry where CI uploads images from ci-cross build job
DefaultCIImageRepository = "gcr.io/kubernetes-ci-images"
)
var (

View File

@ -63,7 +63,7 @@ func EnsureProxyAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Inte
}
proxyDaemonSetBytes, err := kubeadmutil.ParseTemplate(KubeProxyDaemonSet, struct{ ImageRepository, Arch, Version, ImageOverride, ClusterCIDR, MasterTaintKey, CloudTaintKey string }{
ImageRepository: cfg.ImageRepository,
ImageRepository: cfg.GetControlPlaneImageRepository(),
Arch: runtime.GOARCH,
Version: kubeadmutil.KubernetesVersionToImageTag(cfg.KubernetesVersion),
ImageOverride: cfg.UnifiedControlPlaneImage,

View File

@ -71,7 +71,7 @@ func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.
staticPodSpecs := map[string]v1.Pod{
kubeadmconstants.KubeAPIServer: staticpodutil.ComponentPod(v1.Container{
Name: kubeadmconstants.KubeAPIServer,
Image: images.GetCoreImage(kubeadmconstants.KubeAPIServer, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
Image: images.GetCoreImage(kubeadmconstants.KubeAPIServer, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
Command: getAPIServerCommand(cfg, k8sVersion),
VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeAPIServer),
LivenessProbe: staticpodutil.ComponentProbe(int(cfg.API.BindPort), "/healthz", v1.URISchemeHTTPS),
@ -80,7 +80,7 @@ func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.
}, mounts.GetVolumes(kubeadmconstants.KubeAPIServer)),
kubeadmconstants.KubeControllerManager: staticpodutil.ComponentPod(v1.Container{
Name: kubeadmconstants.KubeControllerManager,
Image: images.GetCoreImage(kubeadmconstants.KubeControllerManager, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
Image: images.GetCoreImage(kubeadmconstants.KubeControllerManager, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
Command: getControllerManagerCommand(cfg, k8sVersion),
VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeControllerManager),
LivenessProbe: staticpodutil.ComponentProbe(10252, "/healthz", v1.URISchemeHTTP),
@ -89,7 +89,7 @@ func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.
}, mounts.GetVolumes(kubeadmconstants.KubeControllerManager)),
kubeadmconstants.KubeScheduler: staticpodutil.ComponentPod(v1.Container{
Name: kubeadmconstants.KubeScheduler,
Image: images.GetCoreImage(kubeadmconstants.KubeScheduler, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
Image: images.GetCoreImage(kubeadmconstants.KubeScheduler, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
Command: getSchedulerCommand(cfg),
VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeScheduler),
LivenessProbe: staticpodutil.ComponentProbe(10251, "/healthz", v1.URISchemeHTTP),

View File

@ -44,6 +44,11 @@ func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
}
cfg.API.AdvertiseAddress = ip.String()
// Requested version is automatic CI build, thus use KubernetesCI Image Repository for core images
if kubeadmutil.KubernetesIsCIVersion(cfg.KubernetesVersion) {
cfg.CIImageRepository = kubeadmconstants.DefaultCIImageRepository
}
// Validate version argument
ver, err := kubeadmutil.KubernetesReleaseVersion(cfg.KubernetesVersion)
if err != nil {

View File

@ -25,9 +25,10 @@ import (
)
var (
kubeReleaseBucketURL = "https://storage.googleapis.com/kubernetes-release/release"
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
@ -53,22 +54,20 @@ func KubernetesReleaseVersion(version string) (string, error) {
return version, nil
}
return "v" + version, nil
} else if kubeReleaseLabelRegex.MatchString(version) {
url := fmt.Sprintf("%s/%s.txt", kubeReleaseBucketURL, version)
resp, err := http.Get(url)
}
bucketURL, versionLabel, err := splitVersion(version)
if err != nil {
return "", err
}
if kubeReleaseLabelRegex.MatchString(versionLabel) {
url := fmt.Sprintf("%s/%s.txt", bucketURL, versionLabel)
body, err := fetchFromURL(url)
if err != nil {
return "", fmt.Errorf("unable to get URL %q: %s", url, err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("unable to fetch release information. URL: %q Status: %v", url, resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("unable to read content of URL %q: %s", url, err.Error())
return "", err
}
// Re-validate received version and return.
return KubernetesReleaseVersion(strings.Trim(string(body), " \t\n"))
return KubernetesReleaseVersion(body)
}
return "", fmt.Errorf("version %q doesn't match patterns for neither semantic version nor labels (stable, latest, ...)", version)
}
@ -83,3 +82,49 @@ 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: 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 "", "", fmt.Errorf("invalid version %q", version)
}
switch {
case strings.HasPrefix(subs[0][2], "ci"):
// Special case. CI images populated only by ci-cross area
urlSuffix = "ci-cross"
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) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("unable to get URL %q: %s", url, err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("unable to fetch file. URL: %q Status: %v", url, resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("unable to read content of URL %q: %s", url, err.Error())
}
return strings.TrimSpace(string(body)), nil
}

View File

@ -45,7 +45,7 @@ func TestValidVersion(t *testing.T) {
"v1.6.0-alpha.0.536+d60d9f3269288f",
"v1.5.0-alpha.0.1078+1044b6822497da-pull",
"v1.5.0-alpha.1.822+49b9e32fad9f32-pull-gke-gci",
"v1.6.1_coreos.0",
"v1.6.1+coreos.0",
}
for _, s := range validVersions {
ver, err := KubernetesReleaseVersion(s)
@ -165,3 +165,70 @@ func TestVersionToTag(t *testing.T) {
}
}
}
func TestSplitVersion(t *testing.T) {
type T struct {
input string
bucket string
label string
valid bool
}
cases := []T{
// Release area
{"v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
{"v1.8.0-alpha.2.1231+afabd012389d53a", "https://dl.k8s.io/release", "v1.8.0-alpha.2.1231+afabd012389d53a", true},
{"release/v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
{"release/latest-1.7", "https://dl.k8s.io/release", "latest-1.7", true},
// CI builds area, lookup actual builds at ci-cross/*.txt
{"ci-cross/latest", "https://dl.k8s.io/ci-cross", "latest", true},
{"ci/latest-1.7", "https://dl.k8s.io/ci-cross", "latest-1.7", true},
// unknown label in default (release) area: splitVersion validate only areas.
{"unknown-1", "https://dl.k8s.io/release", "unknown-1", true},
// unknown area, not valid input.
{"unknown/latest-1", "", "", false},
}
// kubeReleaseBucketURL can be overriden during network tests, thus ensure
// it will contain value corresponding to expected outcome for this unit test
kubeReleaseBucketURL = "https://dl.k8s.io"
for _, tc := range cases {
bucket, label, err := splitVersion(tc.input)
switch {
case err != nil && tc.valid:
t.Errorf("splitVersion: unexpected error for %q. Error: %v", tc.input, err)
case err == nil && !tc.valid:
t.Errorf("splitVersion: error expected for key %q, but result is %q, %q", tc.input, bucket, label)
case bucket != tc.bucket:
t.Errorf("splitVersion: unexpected bucket result for key %q. Expected: %q Actual: %q", tc.input, tc.bucket, bucket)
case label != tc.label:
t.Errorf("splitVersion: unexpected label result for key %q. Expected: %q Actual: %q", tc.input, tc.label, label)
}
}
}
func TestKubernetesIsCIVersion(t *testing.T) {
type T struct {
input string
expected bool
}
cases := []T{
{"", false},
// Official releases
{"v1.0.0", false},
{"release/v1.0.0", false},
// CI builds
{"ci/latest-1", true},
{"ci-cross/latest", true},
}
for _, tc := range cases {
result := KubernetesIsCIVersion(tc.input)
t.Logf("KubernetesIsCIVersion: Input: %q. Result: %v. Expected: %v", tc.input, result, tc.expected)
if result != tc.expected {
t.Errorf("failed KubernetesIsCIVersion: Input: %q. Result: %v. Expected: %v", tc.input, result, tc.expected)
}
}
}