github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/provider.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provider 5 6 import ( 7 stdcontext "context" 8 "net/url" 9 osexec "os/exec" 10 11 jujuclock "github.com/juju/clock" 12 "github.com/juju/errors" 13 "github.com/juju/jsonschema" 14 "github.com/juju/utils/v3/exec" 15 corev1 "k8s.io/api/core/v1" 16 storagev1 "k8s.io/api/storage/v1" 17 apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 18 k8slabels "k8s.io/apimachinery/pkg/labels" 19 "k8s.io/client-go/dynamic" 20 "k8s.io/client-go/kubernetes" 21 "k8s.io/client-go/rest" 22 23 "github.com/juju/juju/caas" 24 k8s "github.com/juju/juju/caas/kubernetes" 25 k8scloud "github.com/juju/juju/caas/kubernetes/cloud" 26 "github.com/juju/juju/caas/kubernetes/provider/constants" 27 "github.com/juju/juju/caas/kubernetes/provider/utils" 28 k8swatcher "github.com/juju/juju/caas/kubernetes/provider/watcher" 29 "github.com/juju/juju/cloud" 30 "github.com/juju/juju/environs" 31 environsbootstrap "github.com/juju/juju/environs/bootstrap" 32 environscloudspec "github.com/juju/juju/environs/cloudspec" 33 "github.com/juju/juju/environs/config" 34 "github.com/juju/juju/environs/context" 35 ) 36 37 // ClusterMetadataStorageChecker provides functionalities for checking k8s cluster storage and pods details. 38 type ClusterMetadataStorageChecker interface { 39 k8s.ClusterMetadataChecker 40 ListStorageClasses(selector k8slabels.Selector) ([]storagev1.StorageClass, error) 41 ListPods(namespace string, selector k8slabels.Selector) ([]corev1.Pod, error) 42 } 43 44 type kubernetesEnvironProvider struct { 45 environProviderCredentials 46 cmdRunner CommandRunner 47 builtinCloudGetter func(CommandRunner) (cloud.Cloud, error) 48 brokerGetter func(environs.OpenParams) (ClusterMetadataStorageChecker, error) 49 } 50 51 var _ environs.EnvironProvider = (*kubernetesEnvironProvider)(nil) 52 var providerInstance = kubernetesEnvironProvider{ 53 environProviderCredentials: environProviderCredentials{ 54 cmdRunner: defaultRunner{}, 55 builtinCredentialGetter: func(cmdRunner CommandRunner) (cloud.Credential, error) { 56 return attemptMicroK8sCredential(cmdRunner, decideKubeConfigDir) 57 }, 58 }, 59 cmdRunner: defaultRunner{}, 60 builtinCloudGetter: func(cmdRunner CommandRunner) (cloud.Cloud, error) { 61 return attemptMicroK8sCloud(cmdRunner, decideKubeConfigDir) 62 }, 63 brokerGetter: func(args environs.OpenParams) (ClusterMetadataStorageChecker, error) { 64 broker, err := caas.New(stdcontext.TODO(), args) 65 if err != nil { 66 return nil, errors.Trace(err) 67 } 68 69 metaChecker, supported := broker.(ClusterMetadataStorageChecker) 70 if !supported { 71 return nil, errors.NotSupportedf("cluster metadata ") 72 } 73 return metaChecker, nil 74 }, 75 } 76 77 // Version is part of the EnvironProvider interface. 78 func (kubernetesEnvironProvider) Version() int { 79 return 0 80 } 81 82 // CommandRunner allows to run commands on the underlying system 83 type CommandRunner interface { 84 RunCommands(run exec.RunParams) (*exec.ExecResponse, error) 85 LookPath(string) (string, error) 86 } 87 88 type defaultRunner struct{} 89 90 func (defaultRunner) RunCommands(run exec.RunParams) (*exec.ExecResponse, error) { 91 return exec.RunCommands(run) 92 } 93 94 func (defaultRunner) LookPath(file string) (string, error) { 95 return osexec.LookPath(file) 96 } 97 98 // NewK8sClients returns the k8s clients to access a cluster. 99 // Override for testing. 100 var NewK8sClients = func(c *rest.Config) ( 101 k8sClient kubernetes.Interface, 102 apiextensionsclient apiextensionsclientset.Interface, 103 dynamicClient dynamic.Interface, 104 err error, 105 ) { 106 k8sClient, err = kubernetes.NewForConfig(c) 107 if err != nil { 108 return nil, nil, nil, err 109 } 110 apiextensionsclient, err = apiextensionsclientset.NewForConfig(c) 111 if err != nil { 112 return nil, nil, nil, err 113 } 114 dynamicClient, err = dynamic.NewForConfig(c) 115 if err != nil { 116 return nil, nil, nil, err 117 } 118 return k8sClient, apiextensionsclient, dynamicClient, nil 119 } 120 121 // CloudSpecToK8sRestConfig translates cloudspec to k8s rest config. 122 func CloudSpecToK8sRestConfig(cloudSpec environscloudspec.CloudSpec) (*rest.Config, error) { 123 if cloudSpec.IsControllerCloud { 124 rc, err := rest.InClusterConfig() 125 if err != nil && err != rest.ErrNotInCluster { 126 return nil, errors.Trace(err) 127 } 128 if rc != nil { 129 logger.Tracef("using in-cluster config") 130 return rc, nil 131 } 132 } 133 134 if cloudSpec.Credential == nil { 135 return nil, errors.Errorf("cloud %v has no credential", cloudSpec.Name) 136 } 137 138 var CAData []byte 139 for _, cacert := range cloudSpec.CACertificates { 140 CAData = append(CAData, cacert...) 141 } 142 143 credentialAttrs := cloudSpec.Credential.Attributes() 144 return &rest.Config{ 145 Host: cloudSpec.Endpoint, 146 Username: credentialAttrs[k8scloud.CredAttrUsername], 147 Password: credentialAttrs[k8scloud.CredAttrPassword], 148 BearerToken: credentialAttrs[k8scloud.CredAttrToken], 149 TLSClientConfig: rest.TLSClientConfig{ 150 CertData: []byte(credentialAttrs[k8scloud.CredAttrClientCertificateData]), 151 KeyData: []byte(credentialAttrs[k8scloud.CredAttrClientKeyData]), 152 CAData: CAData, 153 Insecure: cloudSpec.SkipTLSVerify, 154 }, 155 }, nil 156 } 157 158 func newRestClient(cfg *rest.Config) (rest.Interface, error) { 159 return rest.RESTClientFor(cfg) 160 } 161 162 // Open is part of the ContainerEnvironProvider interface. 163 func (p kubernetesEnvironProvider) Open(args environs.OpenParams) (caas.Broker, error) { 164 logger.Debugf("opening model %q.", args.Config.Name()) 165 if err := p.validateCloudSpec(args.Cloud); err != nil { 166 return nil, errors.Annotate(err, "validating cloud spec") 167 } 168 k8sRestConfig, err := CloudSpecToK8sRestConfig(args.Cloud) 169 if err != nil { 170 return nil, errors.Trace(err) 171 } 172 173 if args.Config.Name() != environsbootstrap.ControllerModelName { 174 broker, err := newK8sBroker( 175 args.ControllerUUID, k8sRestConfig, args.Config, args.Config.Name(), NewK8sClients, newRestClient, 176 k8swatcher.NewKubernetesNotifyWatcher, k8swatcher.NewKubernetesStringsWatcher, utils.RandomPrefix, 177 jujuclock.WallClock) 178 if err != nil { 179 return nil, errors.Trace(err) 180 } 181 return broker, nil 182 } 183 184 k8sClient, _, _, err := NewK8sClients(k8sRestConfig) 185 if err != nil { 186 return nil, errors.Trace(err) 187 } 188 189 ns, err := findControllerNamespace(k8sClient, args.ControllerUUID) 190 if errors.IsNotFound(err) { 191 // The controller is currently bootstrapping. 192 return newK8sBroker( 193 args.ControllerUUID, k8sRestConfig, args.Config, "", 194 NewK8sClients, newRestClient, k8swatcher.NewKubernetesNotifyWatcher, k8swatcher.NewKubernetesStringsWatcher, 195 utils.RandomPrefix, jujuclock.WallClock) 196 } else if err != nil { 197 return nil, err 198 } 199 200 return newK8sBroker( 201 args.ControllerUUID, k8sRestConfig, args.Config, ns.Name, 202 NewK8sClients, newRestClient, k8swatcher.NewKubernetesNotifyWatcher, k8swatcher.NewKubernetesStringsWatcher, 203 utils.RandomPrefix, jujuclock.WallClock) 204 } 205 206 // CloudSchema returns the schema for adding new clouds of this type. 207 func (p kubernetesEnvironProvider) CloudSchema() *jsonschema.Schema { 208 return nil 209 } 210 211 // Ping tests the connection to the cloud, to verify the endpoint is valid. 212 func (p kubernetesEnvironProvider) Ping(ctx context.ProviderCallContext, endpoint string) error { 213 return errors.NotImplementedf("Ping") 214 } 215 216 // PrepareConfig is specified in the EnvironProvider interface. 217 func (p kubernetesEnvironProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) { 218 if err := p.validateCloudSpec(args.Cloud); err != nil { 219 return nil, errors.Annotate(err, "validating cloud spec") 220 } 221 // Set the default storage sources. 222 attrs := make(map[string]interface{}) 223 if _, ok := args.Config.StorageDefaultBlockSource(); !ok { 224 attrs[config.StorageDefaultBlockSourceKey] = constants.StorageProviderType 225 } 226 if _, ok := args.Config.StorageDefaultFilesystemSource(); !ok { 227 attrs[config.StorageDefaultFilesystemSourceKey] = constants.StorageProviderType 228 } 229 return args.Config.Apply(attrs) 230 } 231 232 // DetectRegions is specified in the environs.CloudRegionDetector interface. 233 func (p kubernetesEnvironProvider) DetectRegions() ([]cloud.Region, error) { 234 return nil, errors.NotFoundf("regions") 235 } 236 237 func (p kubernetesEnvironProvider) validateCloudSpec(spec environscloudspec.CloudSpec) error { 238 if err := spec.Validate(); err != nil { 239 return errors.Trace(err) 240 } 241 if _, err := url.Parse(spec.Endpoint); err != nil { 242 return errors.NotValidf("endpoint %q", spec.Endpoint) 243 } 244 if spec.Credential == nil { 245 return errors.NotValidf("missing credential") 246 } 247 248 if authType := spec.Credential.AuthType(); !k8scloud.SupportedAuthTypes().Contains(authType) { 249 return errors.NotSupportedf("%q auth-type", authType) 250 } 251 return nil 252 }