karmada/operator/pkg/util/apiclient/idempotency.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()))
}