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  }