karmada/pkg/util/membercluster_client.go

230 lines
7.5 KiB
Go

package util
import (
"context"
"fmt"
"net/http"
"net/url"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
kubeclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/scale"
"k8s.io/klog/v2"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
)
// ClusterClient stands for a cluster Clientset for the given member cluster
type ClusterClient struct {
KubeClient *kubeclientset.Clientset
ClusterName string
}
// DynamicClusterClient stands for a dynamic client for the given member cluster
type DynamicClusterClient struct {
DynamicClientSet dynamic.Interface
ClusterName string
}
// ClusterScaleClient stands for a cluster ClientSet with scale client for the given member cluster
type ClusterScaleClient struct {
KubeClient *kubeclientset.Clientset
ScaleClient scale.ScalesGetter
ClusterName string
}
// Config holds the common attributes that can be passed to a Kubernetes client on
// initialization.
// ClientOption holds the attributes that should be injected to a Kubernetes client.
type ClientOption struct {
// QPS indicates the maximum QPS to the master from this client.
// If it's zero, the created RESTClient will use DefaultQPS: 5
QPS float32
// Burst indicates the maximum burst for throttle.
// If it's zero, the created RESTClient will use DefaultBurst: 10.
Burst int
}
// NewClusterScaleClientSet returns a ClusterScaleClient for the given member cluster.
func NewClusterScaleClientSet(clusterName string, client client.Client) (*ClusterScaleClient, error) {
clusterConfig, err := BuildClusterConfig(clusterName, clusterGetter(client), secretGetter(client))
if err != nil {
return nil, err
}
var clusterScaleClientSet = ClusterScaleClient{ClusterName: clusterName}
if clusterConfig != nil {
hpaClient := kubeclientset.NewForConfigOrDie(clusterConfig)
scaleKindResolver := scale.NewDiscoveryScaleKindResolver(hpaClient.Discovery())
httpClient, err := rest.HTTPClientFor(clusterConfig)
if err != nil {
return nil, err
}
mapper, err := apiutil.NewDiscoveryRESTMapper(clusterConfig, httpClient)
if err != nil {
return nil, err
}
scaleClient, err := scale.NewForConfig(clusterConfig, mapper, dynamic.LegacyAPIPathResolverFunc, scaleKindResolver)
if err != nil {
return nil, err
}
clusterScaleClientSet.KubeClient = hpaClient
clusterScaleClientSet.ScaleClient = scaleClient
}
return &clusterScaleClientSet, nil
}
// NewClusterClientSet returns a ClusterClient for the given member cluster.
func NewClusterClientSet(clusterName string, client client.Client, clientOption *ClientOption) (*ClusterClient, error) {
clusterConfig, err := BuildClusterConfig(clusterName, clusterGetter(client), secretGetter(client))
if err != nil {
return nil, err
}
var clusterClientSet = ClusterClient{ClusterName: clusterName}
if clusterConfig != nil {
if clientOption != nil {
clusterConfig.QPS = clientOption.QPS
clusterConfig.Burst = clientOption.Burst
}
clusterClientSet.KubeClient = kubeclientset.NewForConfigOrDie(clusterConfig)
}
return &clusterClientSet, nil
}
// NewClusterClientSetForAgent returns a ClusterClient for the given member cluster which will be used in karmada agent.
func NewClusterClientSetForAgent(clusterName string, _ client.Client, clientOption *ClientOption) (*ClusterClient, error) {
clusterConfig, err := controllerruntime.GetConfig()
if err != nil {
return nil, fmt.Errorf("error building kubeconfig of member cluster: %s", err.Error())
}
var clusterClientSet = ClusterClient{ClusterName: clusterName}
if clusterConfig != nil {
if clientOption != nil {
clusterConfig.QPS = clientOption.QPS
clusterConfig.Burst = clientOption.Burst
}
clusterClientSet.KubeClient = kubeclientset.NewForConfigOrDie(clusterConfig)
}
return &clusterClientSet, nil
}
// NewClusterDynamicClientSet returns a dynamic client for the given member cluster.
func NewClusterDynamicClientSet(clusterName string, client client.Client) (*DynamicClusterClient, error) {
clusterConfig, err := BuildClusterConfig(clusterName, clusterGetter(client), secretGetter(client))
if err != nil {
return nil, err
}
var clusterClientSet = DynamicClusterClient{ClusterName: clusterName}
if clusterConfig != nil {
clusterClientSet.DynamicClientSet = dynamic.NewForConfigOrDie(clusterConfig)
}
return &clusterClientSet, nil
}
// NewClusterDynamicClientSetForAgent returns a dynamic client for the given member cluster which will be used in karmada agent.
func NewClusterDynamicClientSetForAgent(clusterName string, _ client.Client) (*DynamicClusterClient, error) {
clusterConfig, err := controllerruntime.GetConfig()
if err != nil {
return nil, fmt.Errorf("error building kubeconfig of member cluster: %s", err.Error())
}
var clusterClientSet = DynamicClusterClient{ClusterName: clusterName}
if clusterConfig != nil {
clusterClientSet.DynamicClientSet = dynamic.NewForConfigOrDie(clusterConfig)
}
return &clusterClientSet, nil
}
// BuildClusterConfig return rest config for member cluster.
func BuildClusterConfig(clusterName string,
clusterGetter func(string) (*clusterv1alpha1.Cluster, error),
secretGetter func(string, string) (*corev1.Secret, error)) (*rest.Config, error) {
cluster, err := clusterGetter(clusterName)
if err != nil {
return nil, err
}
apiEndpoint := cluster.Spec.APIEndpoint
if apiEndpoint == "" {
return nil, fmt.Errorf("the api endpoint of cluster %s is empty", clusterName)
}
if cluster.Spec.SecretRef == nil {
return nil, fmt.Errorf("cluster %s does not have a secret", clusterName)
}
secret, err := secretGetter(cluster.Spec.SecretRef.Namespace, cluster.Spec.SecretRef.Name)
if err != nil {
return nil, err
}
token, ok := secret.Data[clusterv1alpha1.SecretTokenKey]
if !ok || len(token) == 0 {
return nil, fmt.Errorf("the secret for cluster %s is missing a non-empty value for %q", clusterName, clusterv1alpha1.SecretTokenKey)
}
// Initialize cluster configuration.
clusterConfig := &rest.Config{
BearerToken: string(token),
Host: apiEndpoint,
}
// Handle TLS configuration.
if cluster.Spec.InsecureSkipTLSVerification {
clusterConfig.TLSClientConfig.Insecure = true
} else {
ca, ok := secret.Data[clusterv1alpha1.SecretCADataKey]
if !ok {
return nil, fmt.Errorf("the secret for cluster %s is missing the CA data key %q", clusterName, clusterv1alpha1.SecretCADataKey)
}
clusterConfig.TLSClientConfig = rest.TLSClientConfig{CAData: ca}
}
// Handle proxy configuration.
if cluster.Spec.ProxyURL != "" {
proxy, err := url.Parse(cluster.Spec.ProxyURL)
if err != nil {
klog.Errorf("parse proxy error. %v", err)
return nil, err
}
clusterConfig.Proxy = http.ProxyURL(proxy)
if len(cluster.Spec.ProxyHeader) != 0 {
clusterConfig.Wrap(NewProxyHeaderRoundTripperWrapperConstructor(clusterConfig.WrapTransport, cluster.Spec.ProxyHeader))
}
}
return clusterConfig, nil
}
func clusterGetter(client client.Client) func(string) (*clusterv1alpha1.Cluster, error) {
return func(cluster string) (*clusterv1alpha1.Cluster, error) {
return GetCluster(client, cluster)
}
}
func secretGetter(client client.Client) func(string, string) (*corev1.Secret, error) {
return func(namespace string, name string) (*corev1.Secret, error) {
secret := &corev1.Secret{}
err := client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: name}, secret)
return secret, err
}
}