390 lines
16 KiB
Go
390 lines
16 KiB
Go
/*
|
|
Copyright 2023 The Karmada Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package apiclient
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
rbacv1 "k8s.io/api/rbac/v1"
|
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
|
crdsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
clientset "k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/klog/v2"
|
|
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
|
aggregator "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
|
|
|
"github.com/karmada-io/karmada/operator/pkg/constants"
|
|
)
|
|
|
|
var errAllocated = errors.New("provided port is already allocated")
|
|
|
|
// NewCRDsClient is to create a Clientset
|
|
func NewCRDsClient(c *rest.Config) (*crdsclient.Clientset, error) {
|
|
return crdsclient.NewForConfig(c)
|
|
}
|
|
|
|
// NewAPIRegistrationClient is to create an apiregistration ClientSet
|
|
func NewAPIRegistrationClient(c *rest.Config) (aggregator.Interface, error) {
|
|
return aggregator.NewForConfig(c)
|
|
}
|
|
|
|
// CreateNamespace creates given namespace when the namespace is not existing.
|
|
func CreateNamespace(client clientset.Interface, ns *corev1.Namespace) error {
|
|
_, err := client.CoreV1().Namespaces().Get(context.TODO(), ns.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
if !apierrors.IsNotFound(err) {
|
|
return err
|
|
}
|
|
if _, err := client.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
klog.V(5).InfoS("Successfully created namespace", "namespace", ns.GetName())
|
|
return nil
|
|
}
|
|
|
|
// CreateOrUpdateSecret creates a Sercret if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
func CreateOrUpdateSecret(client clientset.Interface, secret *corev1.Secret) error {
|
|
_, err := client.CoreV1().Secrets(secret.GetNamespace()).Create(context.TODO(), secret, metav1.CreateOptions{})
|
|
if err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
return err
|
|
}
|
|
|
|
_, err := client.CoreV1().Secrets(secret.GetNamespace()).Update(context.TODO(), secret, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
klog.V(5).InfoS("Successfully created or updated secret", "secret", secret.GetName())
|
|
return nil
|
|
}
|
|
|
|
// CreateOrUpdateService creates a Service if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
func CreateOrUpdateService(client clientset.Interface, service *corev1.Service) error {
|
|
_, err := client.CoreV1().Services(service.GetNamespace()).Create(context.TODO(), service, metav1.CreateOptions{})
|
|
if err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
// Ignore if the Service is invalid with this error message:
|
|
// Service "apiserver" is invalid: provided Port is already allocated.
|
|
if apierrors.IsInvalid(err) && strings.Contains(err.Error(), errAllocated.Error()) {
|
|
klog.V(2).ErrorS(err, "failed to create or update service", "service", klog.KObj(service))
|
|
return nil
|
|
}
|
|
return fmt.Errorf("unable to create Service: %v", err)
|
|
}
|
|
|
|
older, err := client.CoreV1().Services(service.GetNamespace()).Get(context.TODO(), service.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
service.ResourceVersion = older.ResourceVersion
|
|
service.Spec.ClusterIP = older.Spec.ClusterIP
|
|
service.Spec.ClusterIPs = older.Spec.ClusterIPs
|
|
if _, err := client.CoreV1().Services(service.GetNamespace()).Update(context.TODO(), service, metav1.UpdateOptions{}); err != nil {
|
|
return fmt.Errorf("unable to update Service: %v", err)
|
|
}
|
|
}
|
|
|
|
klog.V(5).InfoS("Successfully created or updated service", "service", service.GetName())
|
|
return nil
|
|
}
|
|
|
|
// CreateOrUpdateDeployment creates a Deployment if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
func CreateOrUpdateDeployment(client clientset.Interface, deployment *appsv1.Deployment) error {
|
|
_, err := client.AppsV1().Deployments(deployment.GetNamespace()).Create(context.TODO(), deployment, metav1.CreateOptions{})
|
|
if err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
return err
|
|
}
|
|
|
|
_, err := client.AppsV1().Deployments(deployment.GetNamespace()).Update(context.TODO(), deployment, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
klog.V(5).InfoS("Successfully created or updated deployment", "deployment", deployment.GetName())
|
|
return nil
|
|
}
|
|
|
|
// CreateOrUpdateMutatingWebhookConfiguration creates a MutatingWebhookConfiguration if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
func CreateOrUpdateMutatingWebhookConfiguration(client clientset.Interface, mwc *admissionregistrationv1.MutatingWebhookConfiguration) error {
|
|
_, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), mwc, metav1.CreateOptions{})
|
|
if err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
return err
|
|
}
|
|
older, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(context.TODO(), mwc.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mwc.ResourceVersion = older.ResourceVersion
|
|
_, err = client.AdmissionregistrationV1().MutatingWebhookConfigurations().Update(context.TODO(), mwc, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
klog.V(5).InfoS("Successfully created or updated mutatingWebhookConfiguration", "mutatingWebhookConfiguration", mwc.GetName())
|
|
return nil
|
|
}
|
|
|
|
// CreateOrUpdateValidatingWebhookConfiguration creates a ValidatingWebhookConfiguration if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
func CreateOrUpdateValidatingWebhookConfiguration(client clientset.Interface, vwc *admissionregistrationv1.ValidatingWebhookConfiguration) error {
|
|
_, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), vwc, metav1.CreateOptions{})
|
|
if err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
return err
|
|
}
|
|
older, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.TODO(), vwc.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vwc.ResourceVersion = older.ResourceVersion
|
|
_, err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Update(context.TODO(), vwc, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
klog.V(5).InfoS("Successfully created or updated validatingWebhookConfiguration", "validatingWebhookConfiguration", vwc.GetName())
|
|
return nil
|
|
}
|
|
|
|
// CreateOrUpdateAPIService creates a APIService if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
func CreateOrUpdateAPIService(apiRegistrationClient aggregator.Interface, apiservice *apiregistrationv1.APIService) error {
|
|
_, err := apiRegistrationClient.ApiregistrationV1().APIServices().Create(context.TODO(), apiservice, metav1.CreateOptions{})
|
|
if err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
return err
|
|
}
|
|
older, err := apiRegistrationClient.ApiregistrationV1().APIServices().Get(context.TODO(), apiservice.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
apiservice.ResourceVersion = older.ResourceVersion
|
|
_, err = apiRegistrationClient.ApiregistrationV1().APIServices().Update(context.TODO(), apiservice, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
klog.V(5).InfoS("Successfully created or updated APIService", "APIService", apiservice.Name)
|
|
return nil
|
|
}
|
|
|
|
// CreateCustomResourceDefinitionIfNeed creates a CustomResourceDefinition if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
func CreateCustomResourceDefinitionIfNeed(client *crdsclient.Clientset, obj *apiextensionsv1.CustomResourceDefinition) error {
|
|
crdClient := client.ApiextensionsV1().CustomResourceDefinitions()
|
|
if _, err := crdClient.Create(context.TODO(), obj, metav1.CreateOptions{}); err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
return err
|
|
}
|
|
|
|
klog.V(5).InfoS("Skip already exist crd", "crd", obj.Name)
|
|
return nil
|
|
}
|
|
|
|
klog.V(5).InfoS("Successfully created crd", "crd", obj.Name)
|
|
return nil
|
|
}
|
|
|
|
// PatchCustomResourceDefinition patches a crd resource.
|
|
func PatchCustomResourceDefinition(client *crdsclient.Clientset, name string, data []byte) error {
|
|
crd := client.ApiextensionsV1().CustomResourceDefinitions()
|
|
if _, err := crd.Patch(context.TODO(), name, types.StrategicMergePatchType, data, metav1.PatchOptions{}); err != nil {
|
|
return err
|
|
}
|
|
|
|
klog.V(5).InfoS("Successfully patched crd", "crd", name)
|
|
return nil
|
|
}
|
|
|
|
// CreateOrUpdateStatefulSet creates a StatefulSet if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
func CreateOrUpdateStatefulSet(client clientset.Interface, statefulSet *appsv1.StatefulSet) error {
|
|
_, err := client.AppsV1().StatefulSets(statefulSet.GetNamespace()).Create(context.TODO(), statefulSet, metav1.CreateOptions{})
|
|
if err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
return err
|
|
}
|
|
|
|
older, err := client.AppsV1().StatefulSets(statefulSet.GetNamespace()).Get(context.TODO(), statefulSet.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
statefulSet.ResourceVersion = older.ResourceVersion
|
|
_, err = client.AppsV1().StatefulSets(statefulSet.GetNamespace()).Update(context.TODO(), statefulSet, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
klog.V(5).InfoS("Successfully created or updated statefulset", "statefulset", statefulSet.GetName())
|
|
return nil
|
|
}
|
|
|
|
// CreateOrUpdateClusterRole creates a Clusterrole if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
func CreateOrUpdateClusterRole(client clientset.Interface, clusterrole *rbacv1.ClusterRole) error {
|
|
_, err := client.RbacV1().ClusterRoles().Create(context.TODO(), clusterrole, metav1.CreateOptions{})
|
|
|
|
if err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
return err
|
|
}
|
|
|
|
older, err := client.RbacV1().ClusterRoles().Get(context.TODO(), clusterrole.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
clusterrole.ResourceVersion = older.ResourceVersion
|
|
_, err = client.RbacV1().ClusterRoles().Update(context.TODO(), clusterrole, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
klog.V(4).InfoS("Successfully created or updated clusterrole", "clusterrole", clusterrole.GetName())
|
|
return nil
|
|
}
|
|
|
|
// CreateOrUpdateClusterRoleBinding creates a Clusterrolebinding if the target resource doesn't exist.
|
|
// If the resource exists already, this function will update the resource instead.
|
|
func CreateOrUpdateClusterRoleBinding(client clientset.Interface, clusterrolebinding *rbacv1.ClusterRoleBinding) error {
|
|
_, err := client.RbacV1().ClusterRoleBindings().Create(context.TODO(), clusterrolebinding, metav1.CreateOptions{})
|
|
|
|
if err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
return err
|
|
}
|
|
|
|
older, err := client.RbacV1().ClusterRoleBindings().Get(context.TODO(), clusterrolebinding.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
clusterrolebinding.ResourceVersion = older.ResourceVersion
|
|
_, err = client.RbacV1().ClusterRoleBindings().Update(context.TODO(), clusterrolebinding, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
klog.V(4).InfoS("Successfully created or updated clusterrolebinding", "clusterrolebinding", clusterrolebinding.GetName())
|
|
return nil
|
|
}
|
|
|
|
// DeleteDeploymentIfHasLabels deletes a Deployment that exists the given labels.
|
|
func DeleteDeploymentIfHasLabels(client clientset.Interface, name, namespace string, ls labels.Set) error {
|
|
deployment, err := client.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
klog.V(4).InfoS("Can not delete Deployment, it was not fount", "Deployment", name)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if match := containsLabels(deployment.ObjectMeta, ls); !match {
|
|
klog.V(4).InfoS("Can not delete Deployment, it doesn't have given label", "Deployment", name, "label", constants.KarmadaOperatorLabelKeyName)
|
|
return nil
|
|
}
|
|
return client.AppsV1().Deployments(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
|
|
}
|
|
|
|
// DeleteStatefulSetIfHasLabels deletes a StatefulSet that exists the given labels.
|
|
func DeleteStatefulSetIfHasLabels(client clientset.Interface, name, namespace string, ls labels.Set) error {
|
|
sts, err := client.AppsV1().StatefulSets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
klog.V(4).InfoS("Can not delete StatefulSet, it was not fount", "StatefulSet", name)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if match := containsLabels(sts.ObjectMeta, ls); !match {
|
|
klog.V(4).InfoS("Can not delete StatefulSet, it doesn't have given label", "StatefulSet", name, "label", constants.KarmadaOperatorLabelKeyName)
|
|
return nil
|
|
}
|
|
return client.AppsV1().StatefulSets(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
|
|
}
|
|
|
|
// DeleteSecretIfHasLabels deletes a secret that exists the given labels.
|
|
func DeleteSecretIfHasLabels(client clientset.Interface, name, namespace string, ls labels.Set) error {
|
|
sts, err := client.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
klog.V(2).InfoS("Can not delete Secret, it was not fount", "Secret", name)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if match := containsLabels(sts.ObjectMeta, ls); !match {
|
|
klog.V(4).InfoS("Can not delete Secret, it doesn't have given label", "Secret", name, "label", constants.KarmadaOperatorLabelKeyName)
|
|
return nil
|
|
}
|
|
return client.CoreV1().Secrets(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
|
|
}
|
|
|
|
// DeleteServiceIfHasLabels deletes a service that exists the given labels.
|
|
func DeleteServiceIfHasLabels(client clientset.Interface, name, namespace string, ls labels.Set) error {
|
|
service, err := client.CoreV1().Services(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
klog.V(4).InfoS("Can not delete Service, it was not fount", "Service", name)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if match := containsLabels(service.ObjectMeta, ls); !match {
|
|
klog.V(4).InfoS("Can not delete Service, it doesn't have given label", "Service", name, "label", constants.KarmadaOperatorLabelKeyName)
|
|
return nil
|
|
}
|
|
return client.CoreV1().Services(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
|
|
}
|
|
|
|
// GetService returns service resource with specified name and namespace.
|
|
func GetService(client clientset.Interface, name, namespace string) (*corev1.Service, error) {
|
|
return client.CoreV1().Services(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
|
}
|
|
|
|
func containsLabels(object metav1.ObjectMeta, ls labels.Set) bool {
|
|
return ls.AsSelector().Matches(labels.Set(object.GetLabels()))
|
|
}
|