k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/phases/upgrade/versiongetter.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package upgrade 18 19 import ( 20 "context" 21 "fmt" 22 23 "github.com/pkg/errors" 24 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 versionutil "k8s.io/apimachinery/pkg/util/version" 27 pkgversion "k8s.io/apimachinery/pkg/version" 28 fakediscovery "k8s.io/client-go/discovery/fake" 29 clientset "k8s.io/client-go/kubernetes" 30 "k8s.io/component-base/version" 31 32 "k8s.io/kubernetes/cmd/kubeadm/app/constants" 33 kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" 34 "k8s.io/kubernetes/cmd/kubeadm/app/util/image" 35 ) 36 37 // VersionGetter defines an interface for fetching different versions. 38 // Easy to implement a fake variant of this interface for unit testing 39 type VersionGetter interface { 40 // ClusterVersion should return the version of the cluster i.e. the API Server version 41 ClusterVersion() (string, *versionutil.Version, error) 42 // KubeadmVersion should return the version of the kubeadm CLI 43 KubeadmVersion() (string, *versionutil.Version, error) 44 // VersionFromCILabel should resolve CI labels like `latest`, `stable`, `stable-1.8`, etc. to real versions 45 VersionFromCILabel(string, string) (string, *versionutil.Version, error) 46 // KubeletVersions should return a map with a version and a list of node names that describes how many kubelets there are for that version 47 KubeletVersions() (map[string][]string, error) 48 // ComponentVersions should return a map with a version and a list of node names that describes how many a given control-plane components there are for that version 49 ComponentVersions(string) (map[string][]string, error) 50 } 51 52 // KubeVersionGetter handles the version-fetching mechanism from external sources 53 type KubeVersionGetter struct { 54 client clientset.Interface 55 } 56 57 // NewKubeVersionGetter returns a new instance of KubeVersionGetter 58 func NewKubeVersionGetter(client clientset.Interface) VersionGetter { 59 return &KubeVersionGetter{ 60 client: client, 61 } 62 } 63 64 // ClusterVersion gets API server version 65 func (g *KubeVersionGetter) ClusterVersion() (string, *versionutil.Version, error) { 66 var ( 67 clusterVersionInfo *pkgversion.Info 68 err error 69 ) 70 // If we are dry-running, do not attempt to fetch the /version resource and just return 71 // the stored FakeServerVersion, which is done when constructing the dry-run client in 72 // common.go#getClient() 73 // The problem here is that during upgrade dry-run client reactors are backed by a dynamic client 74 // via NewClientBackedDryRunGetterFromKubeconfig() and for GetActions there seems to be no analog to 75 // Discovery().Serverversion() resource for a dynamic client(?). 76 fakeclientDiscovery, ok := g.client.Discovery().(*fakediscovery.FakeDiscovery) 77 if ok { 78 clusterVersionInfo = fakeclientDiscovery.FakedServerVersion 79 } else { 80 clusterVersionInfo, err = g.client.Discovery().ServerVersion() 81 if err != nil { 82 return "", nil, errors.Wrap(err, "Couldn't fetch cluster version from the API Server") 83 } 84 } 85 86 clusterVersion, err := versionutil.ParseSemantic(clusterVersionInfo.String()) 87 if err != nil { 88 return "", nil, errors.Wrap(err, "Couldn't parse cluster version") 89 } 90 return clusterVersionInfo.String(), clusterVersion, nil 91 } 92 93 // KubeadmVersion gets kubeadm version 94 func (g *KubeVersionGetter) KubeadmVersion() (string, *versionutil.Version, error) { 95 kubeadmVersionInfo := version.Get() 96 97 kubeadmVersion, err := versionutil.ParseSemantic(kubeadmVersionInfo.String()) 98 if err != nil { 99 return "", nil, errors.Wrap(err, "Couldn't parse kubeadm version") 100 } 101 return kubeadmVersionInfo.String(), kubeadmVersion, nil 102 } 103 104 // VersionFromCILabel resolves a version label like "latest" or "stable" to an actual version using the public Kubernetes CI uploads 105 func (g *KubeVersionGetter) VersionFromCILabel(ciVersionLabel, description string) (string, *versionutil.Version, error) { 106 versionStr, err := kubeadmutil.KubernetesReleaseVersion(ciVersionLabel) 107 if err != nil { 108 return "", nil, errors.Wrapf(err, "Couldn't fetch latest %s from the internet", description) 109 } 110 111 ver, err := versionutil.ParseSemantic(versionStr) 112 if err != nil { 113 return "", nil, errors.Wrapf(err, "Couldn't parse latest %s", description) 114 } 115 return versionStr, ver, nil 116 } 117 118 // KubeletVersions gets the versions of the kubelets in the cluster. 119 func (g *KubeVersionGetter) KubeletVersions() (map[string][]string, error) { 120 nodes, err := g.client.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) 121 if err != nil { 122 return nil, errors.New("couldn't list all nodes in cluster") 123 } 124 125 // map kubelet version to a list of node names 126 kubeletVersions := make(map[string][]string) 127 for _, node := range nodes.Items { 128 kver := node.Status.NodeInfo.KubeletVersion 129 kubeletVersions[kver] = append(kubeletVersions[kver], node.Name) 130 } 131 return kubeletVersions, nil 132 } 133 134 // ComponentVersions gets the versions of the control-plane components in the cluster. 135 // The name parameter is the name of the component to get the versions for. 136 // The function returns a map with the version as the key and a list of node names as the value. 137 func (g *KubeVersionGetter) ComponentVersions(name string) (map[string][]string, error) { 138 podList, err := g.client.CoreV1().Pods(metav1.NamespaceSystem).List( 139 context.TODO(), 140 metav1.ListOptions{ 141 LabelSelector: fmt.Sprintf("component=%s,tier=%s", name, constants.ControlPlaneTier), 142 }, 143 ) 144 if err != nil { 145 return nil, errors.Wrap(err, "couldn't list pods in cluster") 146 } 147 148 componentVersions := make(map[string][]string) 149 for _, pod := range podList.Items { 150 tag := convertImageTagMetadataToSemver(image.TagFromImage(pod.Spec.Containers[0].Image)) 151 componentVersions[tag] = append(componentVersions[tag], pod.Spec.NodeName) 152 } 153 return componentVersions, nil 154 } 155 156 // OfflineVersionGetter will use the version provided or 157 type OfflineVersionGetter struct { 158 VersionGetter 159 version string 160 } 161 162 // NewOfflineVersionGetter wraps a VersionGetter and skips online communication if default information is supplied. 163 // Version can be "" and the behavior will be identical to the versionGetter passed in. 164 func NewOfflineVersionGetter(versionGetter VersionGetter, version string) VersionGetter { 165 return &OfflineVersionGetter{ 166 VersionGetter: versionGetter, 167 version: version, 168 } 169 } 170 171 // VersionFromCILabel will return the version that was passed into the struct 172 func (o *OfflineVersionGetter) VersionFromCILabel(ciVersionLabel, description string) (string, *versionutil.Version, error) { 173 if o.version == "" { 174 return o.VersionGetter.VersionFromCILabel(ciVersionLabel, description) 175 } 176 ver, err := versionutil.ParseSemantic(o.version) 177 if err != nil { 178 return "", nil, errors.Wrapf(err, "Couldn't parse version %s", description) 179 } 180 return o.version, ver, nil 181 }