mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-05-24 10:02:57 +00:00
285 lines
9.3 KiB
Go
285 lines
9.3 KiB
Go
package helper
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
workv1client "github.com/open-cluster-management/api/client/work/clientset/versioned/typed/work/v1"
|
|
workapiv1 "github.com/open-cluster-management/api/work/v1"
|
|
"github.com/openshift/library-go/pkg/controller/factory"
|
|
"github.com/openshift/library-go/pkg/operator/resource/resourcehelper"
|
|
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
|
"k8s.io/apimachinery/pkg/api/equality"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/dynamic"
|
|
"k8s.io/client-go/util/retry"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
const (
|
|
// unknownKind is returned by resourcehelper.GuessObjectGroupVersionKind() when it
|
|
// cannot tell the kind of the given object
|
|
unknownKind = "<unknown>"
|
|
)
|
|
|
|
var (
|
|
genericScheme = runtime.NewScheme()
|
|
)
|
|
|
|
func init() {
|
|
// add apiextensions v1beta1 to scheme to support CustomResourceDefinition v1beta1
|
|
_ = apiextensionsv1beta1.AddToScheme(genericScheme)
|
|
}
|
|
|
|
// MergeManifestConditions return a new ManifestCondition array which merges the existing manifest
|
|
// conditions and the new manifest conditions. Rules to match ManifestCondition between two arrays:
|
|
// 1. match the manifest condition with the whole ManifestResourceMeta;
|
|
// 2. if not matched, try to match with properties other than ordinal in ManifestResourceMeta
|
|
// If no existing manifest condition is matched, the new manifest condition will be used.
|
|
func MergeManifestConditions(conditions, newConditions []workapiv1.ManifestCondition) []workapiv1.ManifestCondition {
|
|
merged := []workapiv1.ManifestCondition{}
|
|
|
|
// build search indices
|
|
metaIndex := map[workapiv1.ManifestResourceMeta]workapiv1.ManifestCondition{}
|
|
metaWithoutOridinalIndex := map[workapiv1.ManifestResourceMeta]workapiv1.ManifestCondition{}
|
|
|
|
duplicated := []workapiv1.ManifestResourceMeta{}
|
|
for _, condition := range conditions {
|
|
metaIndex[condition.ResourceMeta] = condition
|
|
if metaWithoutOridinal := resetOrdinal(condition.ResourceMeta); metaWithoutOridinal != (workapiv1.ManifestResourceMeta{}) {
|
|
if _, exists := metaWithoutOridinalIndex[metaWithoutOridinal]; exists {
|
|
duplicated = append(duplicated, metaWithoutOridinal)
|
|
} else {
|
|
metaWithoutOridinalIndex[metaWithoutOridinal] = condition
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove metaWithoutOridinal from index if it is not unique
|
|
for _, metaWithoutOridinal := range duplicated {
|
|
delete(metaWithoutOridinalIndex, metaWithoutOridinal)
|
|
}
|
|
|
|
// try to match and merge manifest conditions
|
|
for _, newCondition := range newConditions {
|
|
// match with ResourceMeta
|
|
condition, ok := metaIndex[newCondition.ResourceMeta]
|
|
|
|
// match with properties in ResourceMeta other than ordinal if not found yet
|
|
if !ok {
|
|
condition, ok = metaWithoutOridinalIndex[resetOrdinal(newCondition.ResourceMeta)]
|
|
}
|
|
|
|
// if there is existing condition, merge it with new condition
|
|
if ok {
|
|
merged = append(merged, mergeManifestCondition(condition, newCondition))
|
|
continue
|
|
}
|
|
|
|
// otherwise use the new condition
|
|
for i := range newCondition.Conditions {
|
|
newCondition.Conditions[i].LastTransitionTime = metav1.NewTime(time.Now())
|
|
}
|
|
|
|
merged = append(merged, newCondition)
|
|
}
|
|
|
|
return merged
|
|
}
|
|
|
|
func resetOrdinal(meta workapiv1.ManifestResourceMeta) workapiv1.ManifestResourceMeta {
|
|
return workapiv1.ManifestResourceMeta{
|
|
Group: meta.Group,
|
|
Version: meta.Version,
|
|
Kind: meta.Kind,
|
|
Resource: meta.Resource,
|
|
Name: meta.Name,
|
|
Namespace: meta.Namespace,
|
|
}
|
|
}
|
|
|
|
func mergeManifestCondition(condition, newCondition workapiv1.ManifestCondition) workapiv1.ManifestCondition {
|
|
return workapiv1.ManifestCondition{
|
|
ResourceMeta: newCondition.ResourceMeta,
|
|
Conditions: MergeStatusConditions(condition.Conditions, newCondition.Conditions),
|
|
}
|
|
}
|
|
|
|
// MergeStatusConditions returns a new status condition array with merged status conditions. It is based on newConditions,
|
|
// and merges the corresponding existing conditions if exists.
|
|
func MergeStatusConditions(conditions []metav1.Condition, newConditions []metav1.Condition) []metav1.Condition {
|
|
merged := []metav1.Condition{}
|
|
|
|
for _, condition := range conditions {
|
|
merged = append(merged, condition)
|
|
}
|
|
|
|
for _, condition := range newConditions {
|
|
// merge two conditions if necessary
|
|
meta.SetStatusCondition(&merged, condition)
|
|
}
|
|
|
|
return merged
|
|
}
|
|
|
|
type UpdateManifestWorkStatusFunc func(status *workapiv1.ManifestWorkStatus) error
|
|
|
|
func UpdateManifestWorkStatus(
|
|
ctx context.Context,
|
|
client workv1client.ManifestWorkInterface,
|
|
workName string,
|
|
updateFuncs ...UpdateManifestWorkStatusFunc) (*workapiv1.ManifestWorkStatus, bool, error) {
|
|
updated := false
|
|
var updatedWorkStatus *workapiv1.ManifestWorkStatus
|
|
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
|
manifestWork, err := client.Get(ctx, workName, metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
oldStatus := &manifestWork.Status
|
|
|
|
newStatus := oldStatus.DeepCopy()
|
|
for _, update := range updateFuncs {
|
|
if err := update(newStatus); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if equality.Semantic.DeepEqual(oldStatus, newStatus) {
|
|
// We return the newStatus which is a deep copy of oldStatus but with all update funcs applied.
|
|
updatedWorkStatus = newStatus
|
|
return nil
|
|
}
|
|
|
|
manifestWork.Status = *newStatus
|
|
updatedManifestWork, err := client.UpdateStatus(ctx, manifestWork, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
updatedWorkStatus = &updatedManifestWork.Status
|
|
updated = err == nil
|
|
return err
|
|
})
|
|
|
|
return updatedWorkStatus, updated, err
|
|
}
|
|
|
|
// DeleteAppliedResources deletes all given applied resources and returns those pending for finalization
|
|
// If the uid recorded in resources is different from what we get by client, ignore the deletion.
|
|
func DeleteAppliedResources(resources []workapiv1.AppliedManifestResourceMeta, dynamicClient dynamic.Interface) ([]workapiv1.AppliedManifestResourceMeta, []error) {
|
|
var resourcesPendingFinalization []workapiv1.AppliedManifestResourceMeta
|
|
var errs []error
|
|
|
|
for _, resource := range resources {
|
|
gvr := schema.GroupVersionResource{Group: resource.Group, Version: resource.Version, Resource: resource.Resource}
|
|
u, err := dynamicClient.
|
|
Resource(gvr).
|
|
Namespace(resource.Namespace).
|
|
Get(context.TODO(), resource.Name, metav1.GetOptions{})
|
|
if errors.IsNotFound(err) {
|
|
klog.V(2).Infof("Resource %v with key %s/%s is removed Successfully", gvr, resource.Namespace, resource.Name)
|
|
continue
|
|
}
|
|
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf(
|
|
"Failed to get resource %v with key %s/%s: %w",
|
|
gvr, resource.Namespace, resource.Name, err))
|
|
continue
|
|
}
|
|
|
|
if resource.UID != string(u.GetUID()) {
|
|
// the traced instance has been deleted, and forget this item.
|
|
continue
|
|
}
|
|
|
|
if u.GetDeletionTimestamp() != nil && !u.GetDeletionTimestamp().IsZero() {
|
|
resourcesPendingFinalization = append(resourcesPendingFinalization, resource)
|
|
continue
|
|
}
|
|
|
|
// delete the resource which is not deleted yet
|
|
uid := types.UID(resource.UID)
|
|
err = dynamicClient.
|
|
Resource(gvr).
|
|
Namespace(resource.Namespace).
|
|
Delete(context.TODO(), resource.Name, metav1.DeleteOptions{
|
|
Preconditions: &metav1.Preconditions{
|
|
UID: &uid,
|
|
},
|
|
})
|
|
if errors.IsNotFound(err) {
|
|
continue
|
|
}
|
|
// forget this item if the UID precondition check fails
|
|
if errors.IsConflict(err) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf(
|
|
"Failed to delete resource %v with key %s/%s: %w",
|
|
gvr, resource.Namespace, resource.Name, err))
|
|
continue
|
|
}
|
|
|
|
resourcesPendingFinalization = append(resourcesPendingFinalization, resource)
|
|
klog.V(2).Infof("Delete resource %v with key %s/%s", gvr, resource.Namespace, resource.Name)
|
|
}
|
|
|
|
return resourcesPendingFinalization, errs
|
|
}
|
|
|
|
// GuessObjectGroupVersionKind returns GVK for the passed runtime object.
|
|
func GuessObjectGroupVersionKind(object runtime.Object) (*schema.GroupVersionKind, error) {
|
|
gvk := resourcehelper.GuessObjectGroupVersionKind(object)
|
|
// return gvk if found
|
|
if gvk.Kind != unknownKind {
|
|
return &gvk, nil
|
|
}
|
|
|
|
// otherwise fall back to genericScheme
|
|
if kinds, _, _ := genericScheme.ObjectKinds(object); len(kinds) > 0 {
|
|
return &kinds[0], nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("cannot get gvk of %v", object)
|
|
}
|
|
|
|
// RemoveFinalizer removes a finalizer from the list. It mutates its input.
|
|
func RemoveFinalizer(object runtime.Object, finalizerName string) {
|
|
accessor, _ := meta.Accessor(object)
|
|
finalizers := accessor.GetFinalizers()
|
|
newFinalizers := []string{}
|
|
for i := range finalizers {
|
|
if finalizers[i] == finalizerName {
|
|
continue
|
|
}
|
|
newFinalizers = append(newFinalizers, finalizers[i])
|
|
}
|
|
accessor.SetFinalizers(newFinalizers)
|
|
}
|
|
|
|
// AppliedManifestworkQueueKeyFunc return manifestwork key from appliedmanifestwork
|
|
func AppliedManifestworkQueueKeyFunc(hubhash string) factory.ObjectQueueKeyFunc {
|
|
return func(obj runtime.Object) string {
|
|
accessor, _ := meta.Accessor(obj)
|
|
if !strings.HasPrefix(accessor.GetName(), hubhash) {
|
|
return ""
|
|
}
|
|
return strings.TrimPrefix(accessor.GetName(), hubhash+"-")
|
|
}
|
|
}
|
|
|
|
// HubHash returns a hash of hubserver
|
|
// NOTE: the length of hash string is 64, meaning the length of manifestwork name should be less than 189
|
|
func HubHash(hubServer string) string {
|
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(hubServer)))
|
|
}
|