220 lines
7.8 KiB
Go
220 lines
7.8 KiB
Go
package objectwatcher
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
|
|
"github.com/karmada-io/karmada/pkg/util"
|
|
)
|
|
|
|
/*
|
|
This code is directly lifted from the kubefed codebase. It's a list of functions to update the desired object with values retained
|
|
from the cluster object.
|
|
For reference: https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/sync/dispatch/retain.go#L27-L133
|
|
*/
|
|
|
|
const (
|
|
// SecretsField indicates the 'secrets' field of a service account
|
|
SecretsField = "secrets"
|
|
)
|
|
|
|
// RetainClusterFields updates the desired object with values retained
|
|
// from the cluster object.
|
|
func RetainClusterFields(desiredObj, clusterObj *unstructured.Unstructured) error {
|
|
targetKind := desiredObj.GetKind()
|
|
// Pass the same ResourceVersion as in the cluster object for update operation, otherwise operation will fail.
|
|
desiredObj.SetResourceVersion(clusterObj.GetResourceVersion())
|
|
|
|
// Retain finalizers since they will typically be set by
|
|
// controllers in a member cluster. It is still possible to set the fields
|
|
// via overrides.
|
|
desiredObj.SetFinalizers(clusterObj.GetFinalizers())
|
|
// Merge annotations since they will typically be set by controllers in a member cluster
|
|
// and be set by user in karmada-controller-plane.
|
|
util.MergeAnnotations(desiredObj, clusterObj)
|
|
|
|
switch targetKind {
|
|
case util.PodKind:
|
|
return retainPodFields(desiredObj, clusterObj)
|
|
case util.ServiceKind:
|
|
return retainServiceFields(desiredObj, clusterObj)
|
|
case util.ServiceAccountKind:
|
|
return retainServiceAccountFields(desiredObj, clusterObj)
|
|
case util.PersistentVolumeClaimKind:
|
|
return retainPersistentVolumeClaimFields(desiredObj, clusterObj)
|
|
case util.JobKind:
|
|
return retainJobSelectorFields(desiredObj, clusterObj)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func retainPodFields(desiredObj, clusterObj *unstructured.Unstructured) error {
|
|
nodeName, ok, err := unstructured.NestedString(clusterObj.Object, "spec", "nodeName")
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving nodeName from cluster pod: %w", err)
|
|
}
|
|
if ok && nodeName != "" {
|
|
err := unstructured.SetNestedField(desiredObj.Object, nodeName, "spec", "nodeName")
|
|
if err != nil {
|
|
return fmt.Errorf("error setting nodeName for pod: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// retainServiceFields updates the desired service object with values retained from the cluster object.
|
|
func retainServiceFields(desiredObj, clusterObj *unstructured.Unstructured) error {
|
|
// healthCheckNodePort is allocated by APIServer and unchangeable, so it should be retained while updating
|
|
if err := retainServiceHealthCheckNodePort(desiredObj, clusterObj); err != nil {
|
|
return err
|
|
}
|
|
|
|
// ClusterIP and NodePort are allocated to Service by cluster, so retain the same if any while updating
|
|
if err := retainServiceClusterIP(desiredObj, clusterObj); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := retainServiceNodePort(desiredObj, clusterObj); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func retainServiceHealthCheckNodePort(desiredObj, clusterObj *unstructured.Unstructured) error {
|
|
healthCheckNodePort, ok, err := unstructured.NestedInt64(clusterObj.Object, "spec", "healthCheckNodePort")
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving healthCheckNodePort from service: %w", err)
|
|
}
|
|
if ok && healthCheckNodePort > 0 {
|
|
if err = unstructured.SetNestedField(desiredObj.Object, healthCheckNodePort, "spec", "healthCheckNodePort"); err != nil {
|
|
return fmt.Errorf("error setting healthCheckNodePort for service: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func retainServiceClusterIP(desiredObj, clusterObj *unstructured.Unstructured) error {
|
|
clusterIP, ok, err := unstructured.NestedString(clusterObj.Object, "spec", "clusterIP")
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving clusterIP from cluster service: %w", err)
|
|
}
|
|
// !ok could indicate that a cluster ip was not assigned
|
|
if ok && clusterIP != "" {
|
|
err = unstructured.SetNestedField(desiredObj.Object, clusterIP, "spec", "clusterIP")
|
|
if err != nil {
|
|
return fmt.Errorf("error setting clusterIP for service: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func retainServiceNodePort(desiredObj, clusterObj *unstructured.Unstructured) error {
|
|
clusterPorts, ok, err := unstructured.NestedSlice(clusterObj.Object, "spec", "ports")
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving ports from cluster service: %w", err)
|
|
}
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
var desiredPorts []interface{}
|
|
desiredPorts, ok, err = unstructured.NestedSlice(desiredObj.Object, "spec", "ports")
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving ports from service: %w", err)
|
|
}
|
|
if !ok {
|
|
desiredPorts = []interface{}{}
|
|
}
|
|
for desiredIndex := range desiredPorts {
|
|
for clusterIndex := range clusterPorts {
|
|
fPort := desiredPorts[desiredIndex].(map[string]interface{})
|
|
cPort := clusterPorts[clusterIndex].(map[string]interface{})
|
|
if !(fPort["name"] == cPort["name"] && fPort["protocol"] == cPort["protocol"] && fPort["port"] == cPort["port"]) {
|
|
continue
|
|
}
|
|
nodePort, ok := cPort["nodePort"]
|
|
if ok {
|
|
fPort["nodePort"] = nodePort
|
|
}
|
|
}
|
|
}
|
|
err = unstructured.SetNestedSlice(desiredObj.Object, desiredPorts, "spec", "ports")
|
|
if err != nil {
|
|
return fmt.Errorf("error setting ports for service: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// retainServiceAccountFields retains the 'secrets' field of a service account
|
|
// if the desired representation does not include a value for the field. This
|
|
// ensures that the sync controller doesn't continually clear a generated
|
|
// secret from a service account, prompting continual regeneration by the
|
|
// service account controller in the member cluster.
|
|
func retainServiceAccountFields(desiredObj, clusterObj *unstructured.Unstructured) error {
|
|
// Check whether the secrets field is populated in the desired object.
|
|
desiredSecrets, ok, err := unstructured.NestedSlice(desiredObj.Object, SecretsField)
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving secrets from desired service account: %w", err)
|
|
}
|
|
if ok && len(desiredSecrets) > 0 {
|
|
// Field is populated, so an update to the target resource does not
|
|
// risk triggering a race with the service account controller.
|
|
return nil
|
|
}
|
|
|
|
// Retrieve the secrets from the cluster object and retain them.
|
|
secrets, ok, err := unstructured.NestedSlice(clusterObj.Object, SecretsField)
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving secrets from service account: %w", err)
|
|
}
|
|
if ok && len(secrets) > 0 {
|
|
err := unstructured.SetNestedField(desiredObj.Object, secrets, SecretsField)
|
|
if err != nil {
|
|
return fmt.Errorf("error setting secrets for service account: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func retainPersistentVolumeClaimFields(desiredObj, clusterObj *unstructured.Unstructured) error {
|
|
// volumeName is allocated by member cluster and unchangeable, so it should be retained while updating
|
|
volumeName, ok, err := unstructured.NestedString(clusterObj.Object, "spec", "volumeName")
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving volumeName from pvc: %w", err)
|
|
}
|
|
if ok && len(volumeName) > 0 {
|
|
if err = unstructured.SetNestedField(desiredObj.Object, volumeName, "spec", "volumeName"); err != nil {
|
|
return fmt.Errorf("error setting volumeName for pvc: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func retainJobSelectorFields(desiredObj, clusterObj *unstructured.Unstructured) error {
|
|
matchLabels, exist, err := unstructured.NestedStringMap(clusterObj.Object, "spec", "selector", "matchLabels")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exist {
|
|
err = unstructured.SetNestedStringMap(desiredObj.Object, matchLabels, "spec", "selector", "matchLabels")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
templateLabels, exist, err := unstructured.NestedStringMap(clusterObj.Object, "spec", "template", "metadata", "labels")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exist {
|
|
err = unstructured.SetNestedStringMap(desiredObj.Object, templateLabels, "spec", "template", "metadata", "labels")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|