230 lines
7.5 KiB
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
|
|
}
|
|
}
|