github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/cloud.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provider 5 6 import ( 7 jujuclock "github.com/juju/clock" 8 "github.com/juju/errors" 9 "github.com/juju/utils/v3" 10 k8slabels "k8s.io/apimachinery/pkg/labels" 11 12 k8s "github.com/juju/juju/caas/kubernetes" 13 "github.com/juju/juju/caas/kubernetes/clientconfig" 14 k8scloud "github.com/juju/juju/caas/kubernetes/cloud" 15 k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 16 k8sutils "github.com/juju/juju/caas/kubernetes/provider/utils" 17 "github.com/juju/juju/cloud" 18 "github.com/juju/juju/environs" 19 environscloudspec "github.com/juju/juju/environs/cloudspec" 20 "github.com/juju/juju/environs/config" 21 ) 22 23 // ClientConfigFuncGetter returns a function returning az reader that will read a k8s cluster config for a given cluster type 24 type ClientConfigFuncGetter func(string) (clientconfig.ClientConfigFunc, error) 25 26 // GetClusterMetadataFunc returns the ClusterMetadata using the provided ClusterMetadataChecker 27 type GetClusterMetadataFunc func(KubeCloudStorageParams) (*k8s.ClusterMetadata, error) 28 29 // KubeCloudParams defines the parameters used to extract a k8s cluster definition from kubeconfig data. 30 type KubeCloudParams struct { 31 ClusterName string 32 ContextName string 33 CloudName string 34 // CredentialUID ensures RBAC resources are unique. 35 CredentialUID string 36 HostCloudRegion string 37 CaasType string 38 ClientConfigGetter ClientConfigFuncGetter 39 Clock jujuclock.Clock 40 } 41 42 // KubeCloudStorageParams defines the parameters used to determine storage details for a k8s cluster. 43 type KubeCloudStorageParams struct { 44 WorkloadStorage string 45 HostCloudRegion string 46 MetadataChecker k8s.ClusterMetadataChecker 47 GetClusterMetadataFunc GetClusterMetadataFunc 48 } 49 50 // UpdateKubeCloudWithStorage updates the passed Cloud with storage details retrieved from the cloud's cluster. 51 func UpdateKubeCloudWithStorage(k8sCloud cloud.Cloud, storageParams KubeCloudStorageParams) (cloud.Cloud, error) { 52 // Get the cluster metadata and see what storage comes back based on the 53 // preffered rules for metadata. 54 clusterMetadata, err := storageParams.GetClusterMetadataFunc(storageParams) 55 if err != nil { 56 return cloud.Cloud{}, ClusterQueryError{Message: err.Error()} 57 } 58 if clusterMetadata == nil { 59 return cloud.Cloud{}, ClusterQueryError{Message: "cannot get cluster metadata"} 60 } 61 62 if storageParams.HostCloudRegion == "" && clusterMetadata.Cloud != "" { 63 var region string 64 if clusterMetadata.Regions != nil && clusterMetadata.Regions.Size() > 0 { 65 region = clusterMetadata.Regions.SortedValues()[0] 66 } 67 storageParams.HostCloudRegion = cloud.BuildHostCloudRegion(clusterMetadata.Cloud, region) 68 } 69 k8sCloud.HostCloudRegion = storageParams.HostCloudRegion 70 71 if k8sCloud.HostCloudRegion != "" { 72 _, region, err := cloud.SplitHostCloudRegion(k8sCloud.HostCloudRegion) 73 if err != nil { 74 // Shouldn't happen as HostCloudRegion is validated earlier. 75 return cloud.Cloud{}, errors.Trace(err) 76 } 77 if region != "" { 78 k8sCloud.Regions = []cloud.Region{{ 79 Name: region, 80 Endpoint: k8sCloud.Endpoint, 81 }} 82 } 83 } 84 85 // We at least expected operator storage to be available to successfully use 86 // this cloud. 87 if clusterMetadata.OperatorStorageClass == nil { 88 return cloud.Cloud{}, &environs.PreferredStorageNotFound{ 89 Message: "no preferred operator storage found in Kubernetes cluster", 90 } 91 } 92 93 if k8sCloud.Config == nil { 94 k8sCloud.Config = make(map[string]interface{}) 95 } 96 97 k8sCloud.Config[k8sconstants.OperatorStorageKey] = clusterMetadata.OperatorStorageClass.Name 98 k8sCloud.Config[k8sconstants.WorkloadStorageKey] = "" 99 if clusterMetadata.WorkloadStorageClass != nil { 100 k8sCloud.Config[k8sconstants.WorkloadStorageKey] = clusterMetadata.WorkloadStorageClass.Name 101 } 102 return k8sCloud, nil 103 } 104 105 // BaseKubeCloudOpenParams provides a basic OpenParams for a cluster 106 func BaseKubeCloudOpenParams(cloud cloud.Cloud, credential cloud.Credential) (environs.OpenParams, error) { 107 // To get a k8s client, we need a config with minimal information. 108 // It's not used unless operating on a real model but we need to supply it. 109 uuid, err := utils.NewUUID() 110 if err != nil { 111 return environs.OpenParams{}, errors.Trace(err) 112 } 113 attrs := map[string]interface{}{ 114 config.NameKey: "add-cloud", 115 config.TypeKey: "kubernetes", 116 config.UUIDKey: uuid.String(), 117 } 118 cfg, err := config.New(config.UseDefaults, attrs) 119 if err != nil { 120 return environs.OpenParams{}, errors.Trace(err) 121 } 122 123 cloudSpec, err := environscloudspec.MakeCloudSpec(cloud, "", &credential) 124 if err != nil { 125 return environs.OpenParams{}, errors.Trace(err) 126 } 127 openParams := environs.OpenParams{ 128 Cloud: cloudSpec, Config: cfg, 129 } 130 return openParams, nil 131 } 132 133 // FinalizeCloud is part of the environs.CloudFinalizer interface. 134 func (p kubernetesEnvironProvider) FinalizeCloud(ctx environs.FinalizeCloudContext, cld cloud.Cloud) (cloud.Cloud, error) { 135 // We set the clouds auth types to all kubernetes supported auth types here 136 // so that finalize credentials is free to change the credentials of the 137 // bootstrap. See lp-1918486 138 cld.AuthTypes = k8scloud.SupportedAuthTypes() 139 140 // if storage is already defined there is no need to query the cluster 141 if opStorage, ok := cld.Config[k8sconstants.OperatorStorageKey]; ok && opStorage != "" { 142 return cld, nil 143 } 144 145 var credentials cloud.Credential 146 if cld.Name != k8s.K8sCloudMicrok8s { 147 creds, err := p.RegisterCredentials(cld) 148 if err != nil { 149 return cld, err 150 } 151 152 cloudCred, exists := creds[cld.Name] 153 if !exists { 154 return cld, nil 155 } 156 157 credentials = cloudCred.AuthCredentials[creds[cld.Name].DefaultCredential] 158 } else { 159 // Need the credentials, need to query for those details 160 mk8sCloud, err := p.builtinCloudGetter(p.cmdRunner) 161 if err != nil { 162 return cloud.Cloud{}, errors.Trace(err) 163 } 164 cld = mk8sCloud 165 166 creds, err := p.RegisterCredentials(cld) 167 if err != nil { 168 return cld, err 169 } 170 171 credentials = creds[cld.Name].AuthCredentials[creds[cld.Name].DefaultCredential] 172 } 173 174 if cld.SkipTLSVerify { 175 logger.Warningf("k8s cloud %v is configured to skip server certificate validity checks", cld.Name) 176 } 177 178 openParams, err := BaseKubeCloudOpenParams(cld, credentials) 179 if err != nil { 180 return cloud.Cloud{}, errors.Trace(err) 181 } 182 broker, err := p.brokerGetter(openParams) 183 if err != nil { 184 return cloud.Cloud{}, errors.Trace(err) 185 } 186 if cld.Name == k8s.K8sCloudMicrok8s { 187 if err := ensureMicroK8sSuitable(broker); err != nil { 188 return cld, errors.Trace(err) 189 } 190 } 191 storageUpdateParams := KubeCloudStorageParams{ 192 MetadataChecker: broker, 193 GetClusterMetadataFunc: func(storageParams KubeCloudStorageParams) (*k8s.ClusterMetadata, error) { 194 clusterMetadata, err := storageParams.MetadataChecker.GetClusterMetadata("") 195 if err != nil { 196 return nil, errors.Trace(err) 197 } 198 return clusterMetadata, nil 199 }, 200 } 201 202 cld, err = UpdateKubeCloudWithStorage(cld, storageUpdateParams) 203 if err != nil { 204 return cld, errors.Trace(err) 205 } 206 207 if cld.HostCloudRegion == "" { 208 cld.HostCloudRegion = k8s.K8sCloudOther 209 } 210 211 return cld, nil 212 } 213 214 func checkDefaultStorageExist(broker ClusterMetadataStorageChecker) error { 215 storageClasses, err := broker.ListStorageClasses(k8slabels.NewSelector()) 216 if err != nil && !errors.IsNotFound(err) { 217 return errors.Annotate(err, "cannot list storage classes") 218 } 219 for _, sc := range storageClasses { 220 if sc.Annotations["storageclass.kubernetes.io/is-default-class"] == "true" { 221 return nil 222 } 223 } 224 return errors.NotFoundf("default storage") 225 } 226 227 func checkDNSAddonEnabled(broker ClusterMetadataStorageChecker) error { 228 pods, err := broker.ListPods("kube-system", k8sutils.LabelsToSelector(map[string]string{"k8s-app": "kube-dns"})) 229 if err != nil && !errors.IsNotFound(err) { 230 return errors.Annotate(err, "cannot list kube-dns pods") 231 } 232 if len(pods) > 0 { 233 return nil 234 } 235 return errors.NotFoundf("dns pod") 236 } 237 238 func ensureMicroK8sSuitable(broker ClusterMetadataStorageChecker) error { 239 err := checkDefaultStorageExist(broker) 240 if errors.IsNotFound(err) { 241 return errors.New("required storage addon is not enabled") 242 } 243 if err != nil { 244 return errors.Trace(err) 245 } 246 247 err = checkDNSAddonEnabled(broker) 248 if errors.IsNotFound(err) { 249 return errors.New("required dns addon is not enabled") 250 } 251 return errors.Trace(err) 252 }