Merge pull request #1460 from XiShanYongYe-Chang/tidy-up-lifted-file
Tidy up lifted file
This commit is contained in:
commit
aef51c0a48
|
@ -10,6 +10,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
api "github.com/karmada-io/karmada/pkg/apis/cluster"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
)
|
||||
|
||||
const clusterNameMaxLength int = 48
|
||||
|
@ -86,7 +87,7 @@ func ValidateClusterSpec(spec *api.ClusterSpec, fldPath *field.Path) field.Error
|
|||
}
|
||||
|
||||
if len(spec.Taints) > 0 {
|
||||
allErrs = append(allErrs, ValidateClusterTaints(spec.Taints, fldPath.Child("taints"))...)
|
||||
allErrs = append(allErrs, lifted.ValidateClusterTaints(spec.Taints, fldPath.Child("taints"))...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
|
|
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||
"github.com/karmada-io/karmada/pkg/util/informermanager"
|
||||
"github.com/karmada-io/karmada/pkg/util/informermanager/keys"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
"github.com/karmada-io/karmada/pkg/util/names"
|
||||
"github.com/karmada-io/karmada/pkg/util/ratelimiter"
|
||||
"github.com/karmada-io/karmada/pkg/util/restmapper"
|
||||
|
@ -152,7 +153,7 @@ var (
|
|||
|
||||
func (d *ResourceDetector) discoverResources(period time.Duration) {
|
||||
wait.Until(func() {
|
||||
newResources := GetDeletableResources(d.DiscoveryClientSet)
|
||||
newResources := lifted.GetDeletableResources(d.DiscoveryClientSet)
|
||||
for r := range newResources {
|
||||
if d.InformerManager.IsHandlerExist(r, d.EventHandler) || d.gvrDisabled(r) {
|
||||
continue
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/karmada-io/karmada/pkg/estimator/server/nodes"
|
||||
"github.com/karmada-io/karmada/pkg/util"
|
||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||
utilworkload "github.com/karmada-io/karmada/pkg/util/workload"
|
||||
utilworkload "github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
)
|
||||
|
||||
// NodeMaxAvailableReplica calculates max available replicas of a node, based on
|
||||
|
|
|
@ -14,9 +14,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
@ -25,11 +23,9 @@ import (
|
|||
karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
|
||||
"github.com/karmada-io/karmada/pkg/generated/clientset/versioned/scheme"
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/options"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
)
|
||||
|
||||
// Following parseTaints(),validateTaintEffect(),etc. are directly lifted from the Kubernetes codebase.
|
||||
// For reference: https://github.com/kubernetes/kubernetes/blob/ed42bbd722a14640f8b5315a521745e7526ff31b/staging/src/k8s.io/kubectl/pkg/cmd/taint/utils.go
|
||||
|
||||
// Exported taint constant strings to mark the type of the current operation
|
||||
const (
|
||||
MODIFIED = "modified"
|
||||
|
@ -115,7 +111,7 @@ func (o *CommandTaintOption) Complete(args []string) error {
|
|||
return fmt.Errorf("at least one taint update is required")
|
||||
}
|
||||
|
||||
if o.taintsToAdd, o.taintsToRemove, err = parseTaints(taintArgs); err != nil {
|
||||
if o.taintsToAdd, o.taintsToRemove, err = lifted.ParseTaints(taintArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -276,97 +272,6 @@ func (o *CommandTaintOption) parseTaintArgs(args []string) ([]string, error) {
|
|||
return taintArgs, nil
|
||||
}
|
||||
|
||||
// parseTaints takes a spec which is an array and creates slices for new taints to be added, taints to be deleted.
|
||||
// It also validates the spec. For example, the form `<key>` may be used to remove a taint, but not to add one.
|
||||
func parseTaints(spec []string) ([]corev1.Taint, []corev1.Taint, error) {
|
||||
var taints, taintsToRemove []corev1.Taint
|
||||
uniqueTaints := map[corev1.TaintEffect]sets.String{}
|
||||
|
||||
for _, taintSpec := range spec {
|
||||
if strings.HasSuffix(taintSpec, "-") {
|
||||
taintToRemove, err := parseTaint(strings.TrimSuffix(taintSpec, "-"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
taintsToRemove = append(taintsToRemove, corev1.Taint{Key: taintToRemove.Key, Effect: taintToRemove.Effect})
|
||||
} else {
|
||||
newTaint, err := parseTaint(taintSpec)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// validate that the taint has an effect, which is required to add the taint
|
||||
if len(newTaint.Effect) == 0 {
|
||||
return nil, nil, fmt.Errorf("invalid taint spec: %v", taintSpec)
|
||||
}
|
||||
// validate if taint is unique by <key, effect>
|
||||
if len(uniqueTaints[newTaint.Effect]) > 0 && uniqueTaints[newTaint.Effect].Has(newTaint.Key) {
|
||||
return nil, nil, fmt.Errorf("duplicated taints with the same key and effect: %v", newTaint)
|
||||
}
|
||||
// add taint to existingTaints for uniqueness check
|
||||
if len(uniqueTaints[newTaint.Effect]) == 0 {
|
||||
uniqueTaints[newTaint.Effect] = sets.String{}
|
||||
}
|
||||
uniqueTaints[newTaint.Effect].Insert(newTaint.Key)
|
||||
|
||||
taints = append(taints, newTaint)
|
||||
}
|
||||
}
|
||||
return taints, taintsToRemove, nil
|
||||
}
|
||||
|
||||
// parseTaint parses a taint from a string, whose form must be either
|
||||
// '<key>=<value>:<effect>', '<key>:<effect>', or '<key>'.
|
||||
func parseTaint(st string) (corev1.Taint, error) {
|
||||
var taint corev1.Taint
|
||||
|
||||
var key string
|
||||
var value string
|
||||
var effect corev1.TaintEffect
|
||||
|
||||
parts := strings.Split(st, ":")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
key = parts[0]
|
||||
case 2:
|
||||
effect = corev1.TaintEffect(parts[1])
|
||||
if err := validateTaintEffect(effect); err != nil {
|
||||
return taint, err
|
||||
}
|
||||
|
||||
partsKV := strings.Split(parts[0], "=")
|
||||
if len(partsKV) > 2 {
|
||||
return taint, fmt.Errorf("invalid taint spec: %v", st)
|
||||
}
|
||||
key = partsKV[0]
|
||||
if len(partsKV) == 2 {
|
||||
value = partsKV[1]
|
||||
if errs := validation.IsValidLabelValue(value); len(errs) > 0 {
|
||||
return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; "))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return taint, fmt.Errorf("invalid taint spec: %v", st)
|
||||
}
|
||||
|
||||
if errs := validation.IsQualifiedName(key); len(errs) > 0 {
|
||||
return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; "))
|
||||
}
|
||||
|
||||
taint.Key = key
|
||||
taint.Value = value
|
||||
taint.Effect = effect
|
||||
|
||||
return taint, nil
|
||||
}
|
||||
|
||||
func validateTaintEffect(effect corev1.TaintEffect) error {
|
||||
if effect != corev1.TaintEffectNoSchedule && effect != corev1.TaintEffectPreferNoSchedule && effect != corev1.TaintEffectNoExecute {
|
||||
return fmt.Errorf("invalid taint effect: %v, unsupported taint effect", effect)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// reorganizeTaints returns the updated set of taints, taking into account old taints that were not updated,
|
||||
// old taints that were updated, old taints that were deleted, and new taints.
|
||||
func reorganizeTaints(cluster *clusterv1alpha1.Cluster, overwrite bool, taintsToAdd []corev1.Taint, taintsToRemove []corev1.Taint) (string, []corev1.Taint, error) {
|
||||
|
|
|
@ -433,166 +433,3 @@ func TestReorganizeTaints(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTaints(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
spec []string
|
||||
expectedTaints []corev1.Taint
|
||||
expectedTaintsToRemove []corev1.Taint
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "invalid spec format",
|
||||
spec: []string{""},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec format",
|
||||
spec: []string{"foo=abc"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec format",
|
||||
spec: []string{"foo=abc=xyz:NoSchedule"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec format",
|
||||
spec: []string{"foo=abc:xyz:NoSchedule"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec format for adding taint",
|
||||
spec: []string{"foo"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec effect for adding taint",
|
||||
spec: []string{"foo=abc:invalid_effect"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec effect for deleting taint",
|
||||
spec: []string{"foo:invalid_effect-"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "add new taints",
|
||||
spec: []string{"foo=abc:NoSchedule", "bar=abc:NoSchedule", "baz:NoSchedule", "qux:NoSchedule", "foobar=:NoSchedule"},
|
||||
expectedTaints: []corev1.Taint{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: "abc",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "bar",
|
||||
Value: "abc",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "baz",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "qux",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "foobar",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "delete taints",
|
||||
spec: []string{"foo:NoSchedule-", "bar:NoSchedule-", "qux=:NoSchedule-", "dedicated-"},
|
||||
expectedTaintsToRemove: []corev1.Taint{
|
||||
{
|
||||
Key: "foo",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "bar",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "qux",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "dedicated",
|
||||
},
|
||||
},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "add taints and delete taints",
|
||||
spec: []string{"foo=abc:NoSchedule", "bar=abc:NoSchedule", "baz:NoSchedule", "qux:NoSchedule", "foobar=:NoSchedule", "foo:NoSchedule-", "bar:NoSchedule-", "baz=:NoSchedule-"},
|
||||
expectedTaints: []corev1.Taint{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: "abc",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "bar",
|
||||
Value: "abc",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "baz",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "qux",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "foobar",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
expectedTaintsToRemove: []corev1.Taint{
|
||||
{
|
||||
Key: "foo",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "bar",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "baz",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
expectedErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
taints, taintsToRemove, err := parseTaints(c.spec)
|
||||
if c.expectedErr && err == nil {
|
||||
t.Errorf("[%s] expected error for spec %s, but got nothing", c.name, c.spec)
|
||||
}
|
||||
if !c.expectedErr && err != nil {
|
||||
t.Errorf("[%s] expected no error for spec %s, but got: %v", c.name, c.spec, err)
|
||||
}
|
||||
if !reflect.DeepEqual(c.expectedTaints, taints) {
|
||||
t.Errorf("[%s] expected returen taints as %v, but got: %v", c.name, c.expectedTaints, taints)
|
||||
}
|
||||
if !reflect.DeepEqual(c.expectedTaintsToRemove, taintsToRemove) {
|
||||
t.Errorf("[%s] expected return taints to be removed as %v, but got: %v", c.name, c.expectedTaintsToRemove, taintsToRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,12 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||
"github.com/karmada-io/karmada/pkg/util"
|
||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
)
|
||||
|
||||
type dependenciesInterpreter func(object *unstructured.Unstructured) ([]configv1alpha1.DependentObjectReference, error)
|
||||
|
@ -32,7 +34,7 @@ func getDeploymentDependencies(object *unstructured.Unstructured) ([]configv1alp
|
|||
return nil, fmt.Errorf("failed to convert Deployment from unstructured object: %v", err)
|
||||
}
|
||||
|
||||
podObj, err := GetPodFromTemplate(&deploymentObj.Spec.Template, deploymentObj, nil)
|
||||
podObj, err := lifted.GetPodFromTemplate(&deploymentObj.Spec.Template, deploymentObj, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -46,7 +48,7 @@ func getJobDependencies(object *unstructured.Unstructured) ([]configv1alpha1.Dep
|
|||
return nil, fmt.Errorf("failed to convert Job from unstructured object: %v", err)
|
||||
}
|
||||
|
||||
podObj, err := GetPodFromTemplate(&jobObj.Spec.Template, jobObj, nil)
|
||||
podObj, err := lifted.GetPodFromTemplate(&jobObj.Spec.Template, jobObj, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -69,7 +71,7 @@ func getDaemonSetDependencies(object *unstructured.Unstructured) ([]configv1alph
|
|||
return nil, fmt.Errorf("failed to convert DaemonSet from unstructured object: %v", err)
|
||||
}
|
||||
|
||||
podObj, err := GetPodFromTemplate(&daemonSetObj.Spec.Template, daemonSetObj, nil)
|
||||
podObj, err := lifted.GetPodFromTemplate(&daemonSetObj.Spec.Template, daemonSetObj, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -83,7 +85,7 @@ func getStatefulSetDependencies(object *unstructured.Unstructured) ([]configv1al
|
|||
return nil, fmt.Errorf("failed to convert StatefulSet from unstructured object: %v", err)
|
||||
}
|
||||
|
||||
podObj, err := GetPodFromTemplate(&statefulSetObj.Spec.Template, statefulSetObj, nil)
|
||||
podObj, err := lifted.GetPodFromTemplate(&statefulSetObj.Spec.Template, statefulSetObj, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -116,3 +118,21 @@ func getDependenciesFromPodTemplate(podObj *corev1.Pod) ([]configv1alpha1.Depend
|
|||
|
||||
return dependentObjectRefs, nil
|
||||
}
|
||||
|
||||
func getSecretNames(pod *corev1.Pod) sets.String {
|
||||
result := sets.NewString()
|
||||
lifted.VisitPodSecretNames(pod, func(name string) bool {
|
||||
result.Insert(name)
|
||||
return true
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
func getConfigMapNames(pod *corev1.Pod) sets.String {
|
||||
result := sets.NewString()
|
||||
lifted.VisitPodConfigmapNames(pod, func(name string) bool {
|
||||
result.Insert(name)
|
||||
return true
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/karmada-io/karmada/pkg/util"
|
||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
)
|
||||
|
||||
// retentionInterpreter is the function that retains values from "observed" object.
|
||||
|
@ -18,24 +19,13 @@ type retentionInterpreter func(desired *unstructured.Unstructured, observed *uns
|
|||
func getAllDefaultRetentionInterpreter() map[schema.GroupVersionKind]retentionInterpreter {
|
||||
s := make(map[schema.GroupVersionKind]retentionInterpreter)
|
||||
s[corev1.SchemeGroupVersion.WithKind(util.PodKind)] = retainPodFields
|
||||
s[corev1.SchemeGroupVersion.WithKind(util.ServiceKind)] = retainServiceFields
|
||||
s[corev1.SchemeGroupVersion.WithKind(util.ServiceAccountKind)] = retainServiceAccountFields
|
||||
s[corev1.SchemeGroupVersion.WithKind(util.ServiceKind)] = lifted.RetainServiceFields
|
||||
s[corev1.SchemeGroupVersion.WithKind(util.ServiceAccountKind)] = lifted.RetainServiceAccountFields
|
||||
s[corev1.SchemeGroupVersion.WithKind(util.PersistentVolumeClaimKind)] = retainPersistentVolumeClaimFields
|
||||
s[batchv1.SchemeGroupVersion.WithKind(util.JobKind)] = retainJobSelectorFields
|
||||
return s
|
||||
}
|
||||
|
||||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
func retainPodFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
desiredPod, err := helper.ConvertToPod(desired)
|
||||
if err != nil {
|
||||
|
@ -68,80 +58,6 @@ func retainPodFields(desired, observed *unstructured.Unstructured) (*unstructure
|
|||
return unstructuredObj, nil
|
||||
}
|
||||
|
||||
// retainServiceFields updates the desired service object with values retained from the cluster object.
|
||||
func retainServiceFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
// healthCheckNodePort is allocated by APIServer and unchangeable, so it should be retained while updating
|
||||
if err := retainServiceHealthCheckNodePort(desired, observed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ClusterIP is allocated to Service by cluster, so retain the same if any while updating
|
||||
if err := retainServiceClusterIP(desired, observed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return desired, nil
|
||||
}
|
||||
|
||||
func retainServiceHealthCheckNodePort(desired, observed *unstructured.Unstructured) error {
|
||||
healthCheckNodePort, ok, err := unstructured.NestedInt64(observed.Object, "spec", "healthCheckNodePort")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving healthCheckNodePort from service: %w", err)
|
||||
}
|
||||
if ok && healthCheckNodePort > 0 {
|
||||
if err = unstructured.SetNestedField(desired.Object, healthCheckNodePort, "spec", "healthCheckNodePort"); err != nil {
|
||||
return fmt.Errorf("error setting healthCheckNodePort for service: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func retainServiceClusterIP(desired, observed *unstructured.Unstructured) error {
|
||||
clusterIP, ok, err := unstructured.NestedString(observed.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(desired.Object, clusterIP, "spec", "clusterIP")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting clusterIP 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(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
// Check whether the secrets field is populated in the desired object.
|
||||
desiredSecrets, ok, err := unstructured.NestedSlice(desired.Object, SecretsField)
|
||||
if err != nil {
|
||||
return nil, 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 desired, nil
|
||||
}
|
||||
|
||||
// Retrieve the secrets from the cluster object and retain them.
|
||||
secrets, ok, err := unstructured.NestedSlice(observed.Object, SecretsField)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving secrets from service account: %w", err)
|
||||
}
|
||||
if ok && len(secrets) > 0 {
|
||||
err := unstructured.SetNestedField(desired.Object, secrets, SecretsField)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting secrets for service account: %w", err)
|
||||
}
|
||||
}
|
||||
return desired, nil
|
||||
}
|
||||
|
||||
func retainPersistentVolumeClaimFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
// volumeName is allocated by member cluster and unchangeable, so it should be retained while updating
|
||||
volumeName, ok, err := unstructured.NestedString(observed.Object, "spec", "volumeName")
|
||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||
// However the code has been revised for using Lister instead of API interface.
|
||||
// For reference: https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/controller/deployment/util/deployment_util.go
|
||||
|
||||
package workload
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"sort"
|
|
@ -1,4 +1,4 @@
|
|||
package detector
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Copyright 2020 The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
|
||||
// For reference:
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.20/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity.go#L193-L225
|
||||
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
)
|
||||
|
||||
// NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements
|
||||
// labels.Selector.
|
||||
func NodeSelectorRequirementsAsSelector(nsm []corev1.NodeSelectorRequirement) (labels.Selector, error) {
|
||||
if len(nsm) == 0 {
|
||||
return labels.Nothing(), nil
|
||||
}
|
||||
selector := labels.NewSelector()
|
||||
for _, expr := range nsm {
|
||||
var op selection.Operator
|
||||
switch expr.Operator {
|
||||
case corev1.NodeSelectorOpIn:
|
||||
op = selection.In
|
||||
case corev1.NodeSelectorOpNotIn:
|
||||
op = selection.NotIn
|
||||
case corev1.NodeSelectorOpExists:
|
||||
op = selection.Exists
|
||||
case corev1.NodeSelectorOpDoesNotExist:
|
||||
op = selection.DoesNotExist
|
||||
case corev1.NodeSelectorOpGt:
|
||||
op = selection.GreaterThan
|
||||
case corev1.NodeSelectorOpLt:
|
||||
op = selection.LessThan
|
||||
default:
|
||||
return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator)
|
||||
}
|
||||
r, err := labels.NewRequirement(expr.Key, op, expr.Values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selector = selector.Add(*r)
|
||||
}
|
||||
return selector, nil
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// This code is lifted from the kubefed codebase. It's a list of functions to determine whether the provided cluster
|
||||
// object needs to be updated according to the desired object and the recorded version.
|
||||
// For reference: https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/util/propagatedversion.go#L30-L59
|
||||
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
const (
|
||||
generationPrefix = "gen:"
|
||||
resourceVersionPrefix = "rv:"
|
||||
)
|
||||
|
||||
// ObjectVersion retrieves the field type-prefixed value used for
|
||||
// determining currency of the given cluster object.
|
||||
func ObjectVersion(clusterObj *unstructured.Unstructured) string {
|
||||
generation := clusterObj.GetGeneration()
|
||||
if generation != 0 {
|
||||
return fmt.Sprintf("%s%d", generationPrefix, generation)
|
||||
}
|
||||
return fmt.Sprintf("%s%s", resourceVersionPrefix, clusterObj.GetResourceVersion())
|
||||
}
|
||||
|
||||
// ObjectNeedsUpdate determines whether the 2 objects provided cluster
|
||||
// object needs to be updated according to the desired object and the
|
||||
// recorded version.
|
||||
func ObjectNeedsUpdate(desiredObj, clusterObj *unstructured.Unstructured, recordedVersion string) bool {
|
||||
targetVersion := ObjectVersion(clusterObj)
|
||||
|
||||
if recordedVersion != targetVersion {
|
||||
return true
|
||||
}
|
||||
|
||||
// If versions match and the version is sourced from the
|
||||
// generation field, a further check of metadata equivalency is
|
||||
// required.
|
||||
return strings.HasPrefix(targetVersion, generationPrefix) && !objectMetaObjEquivalent(desiredObj, clusterObj)
|
||||
}
|
||||
|
||||
// objectMetaObjEquivalent checks if cluster-independent, user provided data in two given ObjectMeta are equal. If in
|
||||
// the future the ObjectMeta structure is expanded then any field that is not populated
|
||||
// by the api server should be included here.
|
||||
func objectMetaObjEquivalent(a, b metav1.Object) bool {
|
||||
if a.GetName() != b.GetName() {
|
||||
return false
|
||||
}
|
||||
if a.GetNamespace() != b.GetNamespace() {
|
||||
return false
|
||||
}
|
||||
aLabels := a.GetLabels()
|
||||
bLabels := b.GetLabels()
|
||||
if !reflect.DeepEqual(aLabels, bLabels) && (len(aLabels) != 0 || len(bLabels) != 0) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
|
||||
// For reference:
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/controller/controller_utils.go#L466-L495
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/controller/controller_utils.go#L539-L562
|
||||
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func getPodsLabelSet(template *corev1.PodTemplateSpec) labels.Set {
|
||||
desiredLabels := make(labels.Set)
|
||||
for k, v := range template.Labels {
|
||||
desiredLabels[k] = v
|
||||
}
|
||||
return desiredLabels
|
||||
}
|
||||
|
||||
func getPodsFinalizers(template *corev1.PodTemplateSpec) []string {
|
||||
desiredFinalizers := make([]string, len(template.Finalizers))
|
||||
copy(desiredFinalizers, template.Finalizers)
|
||||
return desiredFinalizers
|
||||
}
|
||||
|
||||
func getPodsAnnotationSet(template *corev1.PodTemplateSpec) labels.Set {
|
||||
desiredAnnotations := make(labels.Set)
|
||||
for k, v := range template.Annotations {
|
||||
desiredAnnotations[k] = v
|
||||
}
|
||||
return desiredAnnotations
|
||||
}
|
||||
|
||||
func getPodsPrefix(controllerName string) string {
|
||||
// use the dash (if the name isn't too long) to make the pod name a bit prettier
|
||||
prefix := fmt.Sprintf("%s-", controllerName)
|
||||
if len(ValidatePodName(prefix, true)) != 0 {
|
||||
prefix = controllerName
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
|
||||
// GetPodFromTemplate generates pod object from a template.
|
||||
func GetPodFromTemplate(template *corev1.PodTemplateSpec, parentObject runtime.Object, controllerRef *metav1.OwnerReference) (*corev1.Pod, error) {
|
||||
desiredLabels := getPodsLabelSet(template)
|
||||
desiredFinalizers := getPodsFinalizers(template)
|
||||
desiredAnnotations := getPodsAnnotationSet(template)
|
||||
accessor, err := meta.Accessor(parentObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parentObject does not have ObjectMeta, %v", err)
|
||||
}
|
||||
prefix := getPodsPrefix(accessor.GetName())
|
||||
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: desiredLabels,
|
||||
Annotations: desiredAnnotations,
|
||||
GenerateName: prefix,
|
||||
Finalizers: desiredFinalizers,
|
||||
Namespace: accessor.GetNamespace(),
|
||||
},
|
||||
}
|
||||
if controllerRef != nil {
|
||||
pod.OwnerReferences = append(pod.OwnerReferences, *controllerRef)
|
||||
}
|
||||
pod.Spec = *template.Spec.DeepCopy()
|
||||
return pod, nil
|
||||
}
|
|
@ -16,11 +16,9 @@ limitations under the License.
|
|||
|
||||
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
|
||||
// For reference:
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/helper/helpers.go#L57-L61
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/helper/helpers.go#L169-L192
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/helper/helpers.go#L212-L283
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/v1/helper/helpers.go
|
||||
|
||||
package helper
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -31,12 +29,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// IsQuotaHugePageResourceName returns true if the resource name has the quota
|
||||
// related huge page resource prefix.
|
||||
func IsQuotaHugePageResourceName(name corev1.ResourceName) bool {
|
||||
return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix) || strings.HasPrefix(string(name), corev1.ResourceRequestsHugePagesPrefix)
|
||||
}
|
||||
|
||||
// IsExtendedResourceName returns true if:
|
||||
// 1. the resource name is not in the default namespace;
|
||||
// 2. resource name does not have "requests." prefix,
|
||||
|
@ -54,12 +46,35 @@ func IsExtendedResourceName(name corev1.ResourceName) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// IsPrefixedNativeResource returns true if the resource name is in the
|
||||
// *kubernetes.io/ namespace.
|
||||
func IsPrefixedNativeResource(name corev1.ResourceName) bool {
|
||||
return strings.Contains(string(name), corev1.ResourceDefaultNamespacePrefix)
|
||||
}
|
||||
|
||||
// IsNativeResource returns true if the resource name is in the
|
||||
// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
|
||||
// implicitly in the kubernetes.io/ namespace.
|
||||
func IsNativeResource(name corev1.ResourceName) bool {
|
||||
return !strings.Contains(string(name), "/") ||
|
||||
strings.Contains(string(name), corev1.ResourceDefaultNamespacePrefix)
|
||||
IsPrefixedNativeResource(name)
|
||||
}
|
||||
|
||||
// IsHugePageResourceName returns true if the resource name has the huge page
|
||||
// resource prefix.
|
||||
func IsHugePageResourceName(name corev1.ResourceName) bool {
|
||||
return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix)
|
||||
}
|
||||
|
||||
// IsAttachableVolumeResourceName returns true when the resource name is prefixed in attachable volume
|
||||
func IsAttachableVolumeResourceName(name corev1.ResourceName) bool {
|
||||
return strings.HasPrefix(string(name), corev1.ResourceAttachableVolumesPrefix)
|
||||
}
|
||||
|
||||
// IsQuotaHugePageResourceName returns true if the resource name has the quota
|
||||
// related huge page resource prefix.
|
||||
func IsQuotaHugePageResourceName(name corev1.ResourceName) bool {
|
||||
return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix) || strings.HasPrefix(string(name), corev1.ResourceRequestsHugePagesPrefix)
|
||||
}
|
||||
|
||||
var standardQuotaResources = sets.NewString(
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
|
||||
// For reference:
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/scheduler/util/utils.go#L144-L148
|
||||
|
||||
package lifted
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// IsScalarResourceName validates the resource for Extended, Hugepages, Native and AttachableVolume resources
|
||||
func IsScalarResourceName(name corev1.ResourceName) bool {
|
||||
return IsExtendedResourceName(name) || IsHugePageResourceName(name) ||
|
||||
IsPrefixedNativeResource(name) || IsAttachableVolumeResourceName(name)
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
Copyright 2020 The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// This code is directly lifted from the kubefed codebase.
|
||||
// For reference:
|
||||
// https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/sync/dispatch/retain.go#L48-L155
|
||||
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
const (
|
||||
// SecretsField indicates the 'secrets' field of a service account
|
||||
SecretsField = "secrets"
|
||||
)
|
||||
|
||||
// RetainServiceFields updates the desired service object with values retained from the cluster object.
|
||||
func RetainServiceFields(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
// healthCheckNodePort is allocated by APIServer and unchangeable, so it should be retained while updating
|
||||
if err := retainServiceHealthCheckNodePort(desired, observed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ClusterIP is allocated to Service by cluster, so retain the same if any while updating
|
||||
if err := retainServiceClusterIP(desired, observed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return desired, nil
|
||||
}
|
||||
|
||||
func retainServiceHealthCheckNodePort(desired, observed *unstructured.Unstructured) error {
|
||||
healthCheckNodePort, ok, err := unstructured.NestedInt64(observed.Object, "spec", "healthCheckNodePort")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving healthCheckNodePort from service: %w", err)
|
||||
}
|
||||
if ok && healthCheckNodePort > 0 {
|
||||
if err = unstructured.SetNestedField(desired.Object, healthCheckNodePort, "spec", "healthCheckNodePort"); err != nil {
|
||||
return fmt.Errorf("error setting healthCheckNodePort for service: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func retainServiceClusterIP(desired, observed *unstructured.Unstructured) error {
|
||||
clusterIP, ok, err := unstructured.NestedString(observed.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(desired.Object, clusterIP, "spec", "clusterIP")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting clusterIP 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(desired, observed *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
// Check whether the secrets field is populated in the desired object.
|
||||
desiredSecrets, ok, err := unstructured.NestedSlice(desired.Object, SecretsField)
|
||||
if err != nil {
|
||||
return nil, 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 desired, nil
|
||||
}
|
||||
|
||||
// Retrieve the secrets from the cluster object and retain them.
|
||||
secrets, ok, err := unstructured.NestedSlice(observed.Object, SecretsField)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving secrets from service account: %w", err)
|
||||
}
|
||||
if ok && len(secrets) > 0 {
|
||||
err := unstructured.SetNestedField(desired.Object, secrets, SecretsField)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting secrets for service account: %w", err)
|
||||
}
|
||||
}
|
||||
return desired, nil
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// This code is directly lifted from the Kubernetes codebase.
|
||||
// For reference:
|
||||
// https://github.com/kubernetes/kubernetes/blob/ed42bbd722a14640f8b5315a521745e7526ff31b/staging/src/k8s.io/kubectl/pkg/cmd/taint/utils.go#L37-L126
|
||||
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// ParseTaints takes a spec which is an array and creates slices for new taints to be added, taints to be deleted.
|
||||
// It also validates the spec. For example, the form `<key>` may be used to remove a taint, but not to add one.
|
||||
func ParseTaints(spec []string) ([]corev1.Taint, []corev1.Taint, error) {
|
||||
var taints, taintsToRemove []corev1.Taint
|
||||
uniqueTaints := map[corev1.TaintEffect]sets.String{}
|
||||
|
||||
for _, taintSpec := range spec {
|
||||
if strings.HasSuffix(taintSpec, "-") {
|
||||
taintToRemove, err := parseTaint(strings.TrimSuffix(taintSpec, "-"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
taintsToRemove = append(taintsToRemove, corev1.Taint{Key: taintToRemove.Key, Effect: taintToRemove.Effect})
|
||||
} else {
|
||||
newTaint, err := parseTaint(taintSpec)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// validate that the taint has an effect, which is required to add the taint
|
||||
if len(newTaint.Effect) == 0 {
|
||||
return nil, nil, fmt.Errorf("invalid taint spec: %v", taintSpec)
|
||||
}
|
||||
// validate if taint is unique by <key, effect>
|
||||
if len(uniqueTaints[newTaint.Effect]) > 0 && uniqueTaints[newTaint.Effect].Has(newTaint.Key) {
|
||||
return nil, nil, fmt.Errorf("duplicated taints with the same key and effect: %v", newTaint)
|
||||
}
|
||||
// add taint to existingTaints for uniqueness check
|
||||
if len(uniqueTaints[newTaint.Effect]) == 0 {
|
||||
uniqueTaints[newTaint.Effect] = sets.String{}
|
||||
}
|
||||
uniqueTaints[newTaint.Effect].Insert(newTaint.Key)
|
||||
|
||||
taints = append(taints, newTaint)
|
||||
}
|
||||
}
|
||||
return taints, taintsToRemove, nil
|
||||
}
|
||||
|
||||
// parseTaint parses a taint from a string, whose form must be either
|
||||
// '<key>=<value>:<effect>', '<key>:<effect>', or '<key>'.
|
||||
func parseTaint(st string) (corev1.Taint, error) {
|
||||
var taint corev1.Taint
|
||||
|
||||
var key string
|
||||
var value string
|
||||
var effect corev1.TaintEffect
|
||||
|
||||
parts := strings.Split(st, ":")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
key = parts[0]
|
||||
case 2:
|
||||
effect = corev1.TaintEffect(parts[1])
|
||||
if err := validateTaintEffect(effect); err != nil {
|
||||
return taint, err
|
||||
}
|
||||
|
||||
partsKV := strings.Split(parts[0], "=")
|
||||
if len(partsKV) > 2 {
|
||||
return taint, fmt.Errorf("invalid taint spec: %v", st)
|
||||
}
|
||||
key = partsKV[0]
|
||||
if len(partsKV) == 2 {
|
||||
value = partsKV[1]
|
||||
if errs := validation.IsValidLabelValue(value); len(errs) > 0 {
|
||||
return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; "))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return taint, fmt.Errorf("invalid taint spec: %v", st)
|
||||
}
|
||||
|
||||
if errs := validation.IsQualifiedName(key); len(errs) > 0 {
|
||||
return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; "))
|
||||
}
|
||||
|
||||
taint.Key = key
|
||||
taint.Value = value
|
||||
taint.Effect = effect
|
||||
|
||||
return taint, nil
|
||||
}
|
||||
|
||||
func validateTaintEffect(effect corev1.TaintEffect) error {
|
||||
if effect != corev1.TaintEffectNoSchedule && effect != corev1.TaintEffectPreferNoSchedule && effect != corev1.TaintEffectNoExecute {
|
||||
return fmt.Errorf("invalid taint effect: %v, unsupported taint effect", effect)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// This code is directly lifted from the Kubernetes codebase.
|
||||
// For reference:
|
||||
// https://github.com/kubernetes/kubernetes/blob/ed42bbd722a14640f8b5315a521745e7526ff31b/staging/src/k8s.io/kubectl/pkg/cmd/taint/utils_test.go#L372-L533
|
||||
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestParseTaints(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
spec []string
|
||||
expectedTaints []corev1.Taint
|
||||
expectedTaintsToRemove []corev1.Taint
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "invalid spec format",
|
||||
spec: []string{""},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec format",
|
||||
spec: []string{"foo=abc"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec format",
|
||||
spec: []string{"foo=abc=xyz:NoSchedule"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec format",
|
||||
spec: []string{"foo=abc:xyz:NoSchedule"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec format for adding taint",
|
||||
spec: []string{"foo"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec effect for adding taint",
|
||||
spec: []string{"foo=abc:invalid_effect"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid spec effect for deleting taint",
|
||||
spec: []string{"foo:invalid_effect-"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "add new taints",
|
||||
spec: []string{"foo=abc:NoSchedule", "bar=abc:NoSchedule", "baz:NoSchedule", "qux:NoSchedule", "foobar=:NoSchedule"},
|
||||
expectedTaints: []corev1.Taint{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: "abc",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "bar",
|
||||
Value: "abc",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "baz",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "qux",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "foobar",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "delete taints",
|
||||
spec: []string{"foo:NoSchedule-", "bar:NoSchedule-", "qux=:NoSchedule-", "dedicated-"},
|
||||
expectedTaintsToRemove: []corev1.Taint{
|
||||
{
|
||||
Key: "foo",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "bar",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "qux",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "dedicated",
|
||||
},
|
||||
},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "add taints and delete taints",
|
||||
spec: []string{"foo=abc:NoSchedule", "bar=abc:NoSchedule", "baz:NoSchedule", "qux:NoSchedule", "foobar=:NoSchedule", "foo:NoSchedule-", "bar:NoSchedule-", "baz=:NoSchedule-"},
|
||||
expectedTaints: []corev1.Taint{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: "abc",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "bar",
|
||||
Value: "abc",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "baz",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "qux",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "foobar",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
expectedTaintsToRemove: []corev1.Taint{
|
||||
{
|
||||
Key: "foo",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "bar",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: "baz",
|
||||
Value: "",
|
||||
Effect: corev1.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
expectedErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
taints, taintsToRemove, err := ParseTaints(c.spec)
|
||||
if c.expectedErr && err == nil {
|
||||
t.Errorf("[%s] expected error for spec %s, but got nothing", c.name, c.spec)
|
||||
}
|
||||
if !c.expectedErr && err != nil {
|
||||
t.Errorf("[%s] expected no error for spec %s, but got: %v", c.name, c.spec, err)
|
||||
}
|
||||
if !reflect.DeepEqual(c.expectedTaints, taints) {
|
||||
t.Errorf("[%s] expected returen taints as %v, but got: %v", c.name, c.expectedTaints, taints)
|
||||
}
|
||||
if !reflect.DeepEqual(c.expectedTaintsToRemove, taintsToRemove) {
|
||||
t.Errorf("[%s] expected return taints to be removed as %v, but got: %v", c.name, c.expectedTaintsToRemove, taintsToRemove)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validation
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
@ -40,7 +40,7 @@ func ValidateClusterTaints(taints []corev1.Taint, fldPath *field.Path) field.Err
|
|||
allErrors = append(allErrors, field.Invalid(idxPath.Child("value"), currTaint.Value, strings.Join(errs, ";")))
|
||||
}
|
||||
// validate the taint effect
|
||||
allErrors = append(allErrors, validateTaintEffect(&currTaint.Effect, false, idxPath.Child("effect"))...)
|
||||
allErrors = append(allErrors, validateClusterTaintEffect(&currTaint.Effect, false, idxPath.Child("effect"))...)
|
||||
|
||||
// validate if taint is unique by <key, effect>
|
||||
if len(uniqueTaints[currTaint.Effect]) > 0 && uniqueTaints[currTaint.Effect].Has(currTaint.Key) {
|
||||
|
@ -59,7 +59,7 @@ func ValidateClusterTaints(taints []corev1.Taint, fldPath *field.Path) field.Err
|
|||
return allErrors
|
||||
}
|
||||
|
||||
func validateTaintEffect(effect *corev1.TaintEffect, allowEmpty bool, fldPath *field.Path) field.ErrorList {
|
||||
func validateClusterTaintEffect(effect *corev1.TaintEffect, allowEmpty bool, fldPath *field.Path) field.ErrorList {
|
||||
if !allowEmpty && len(*effect) == 0 {
|
||||
return field.ErrorList{field.Required(fldPath, "")}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes 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.
|
||||
*/
|
||||
|
||||
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
|
||||
// For reference:
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/validation/validation.go#L225-L228
|
||||
|
||||
package lifted
|
||||
|
||||
import apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
|
||||
// ValidatePodName can be used to check whether the given pod name is valid.
|
||||
// Prefix indicates this name will be used as part of generation, in which case
|
||||
// trailing dashes are allowed.
|
||||
var ValidatePodName = apimachineryvalidation.NameIsDNSSubdomain
|
|
@ -23,7 +23,7 @@ limitations under the License.
|
|||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/validation/validation.go#L5073-L5084
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/validation/validation.go#L5651-L5661
|
||||
|
||||
package federatedresourcequota
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
@ -32,8 +32,6 @@ import (
|
|||
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
"github.com/karmada-io/karmada/pkg/webhook/federatedresourcequota/helper"
|
||||
)
|
||||
|
||||
const isNegativeErrorMsg string = apimachineryvalidation.IsNegativeErrorMsg
|
||||
|
@ -46,7 +44,7 @@ func ValidateResourceQuotaResourceName(value string, fldPath *field.Path) field.
|
|||
allErrs := validateResourceName(value, fldPath)
|
||||
|
||||
if len(strings.Split(value, "/")) == 1 {
|
||||
if !helper.IsStandardQuotaResourceName(value) {
|
||||
if !IsStandardQuotaResourceName(value) {
|
||||
return append(allErrs, field.Invalid(fldPath, value, isInvalidQuotaResource))
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +63,7 @@ func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
|
|||
}
|
||||
|
||||
if len(strings.Split(value, "/")) == 1 {
|
||||
if !helper.IsStandardResourceName(value) {
|
||||
if !IsStandardResourceName(value) {
|
||||
return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified"))
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +75,7 @@ func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
|
|||
func ValidateResourceQuantityValue(resource string, value resource.Quantity, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...)
|
||||
if helper.IsIntegerResourceName(resource) {
|
||||
if IsIntegerResourceName(resource) {
|
||||
if value.MilliValue()%int64(1000) != int64(0) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg))
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,31 +15,15 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
|
||||
// For reference: https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/api/v1/pod/util.go,
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/validation/validation.go#L228,
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/kubelet/configmap/configmap_manager.go#L101-L108,
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/kubelet/secret/secret_manager.go#L102-L109
|
||||
//
|
||||
// For reference:
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/api/v1/pod/util.go
|
||||
|
||||
package defaultinterpreter
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// ValidatePodName can be used to check whether the given pod name is valid.
|
||||
// Prefix indicates this name will be used as part of generation, in which case
|
||||
// trailing dashes are allowed.
|
||||
var ValidatePodName = apimachineryvalidation.NameIsDNSSubdomain
|
||||
|
||||
// ContainerType signifies container type
|
||||
type ContainerType int
|
||||
|
||||
|
@ -55,13 +39,24 @@ const (
|
|||
// AllContainers specifies that all containers be visited
|
||||
const AllContainers ContainerType = (InitContainers | Containers | EphemeralContainers)
|
||||
|
||||
// Visitor is called with each object name, and returns true if visiting should continue
|
||||
type Visitor func(name string) (shouldContinue bool)
|
||||
|
||||
// ContainerVisitor is called with each container spec, and returns true
|
||||
// if visiting should continue.
|
||||
type ContainerVisitor func(container *corev1.Container, containerType ContainerType) (shouldContinue bool)
|
||||
|
||||
// Visitor is called with each object name, and returns true if visiting should continue
|
||||
type Visitor func(name string) (shouldContinue bool)
|
||||
|
||||
func skipEmptyNames(visitor Visitor) Visitor {
|
||||
return func(name string) bool {
|
||||
if len(name) == 0 {
|
||||
// continue visiting
|
||||
return true
|
||||
}
|
||||
// delegate to visitor
|
||||
return visitor(name)
|
||||
}
|
||||
}
|
||||
|
||||
// VisitContainers invokes the visitor function with a pointer to every container
|
||||
// spec in the given pod spec with type set in mask. If visitor returns false,
|
||||
// visiting is short-circuited. VisitContainers returns true if visiting completes,
|
||||
|
@ -91,141 +86,6 @@ func VisitContainers(podSpec *corev1.PodSpec, mask ContainerType, visitor Contai
|
|||
return true
|
||||
}
|
||||
|
||||
func visitContainerSecretNames(container *corev1.Container, visitor Visitor) bool {
|
||||
for _, env := range container.EnvFrom {
|
||||
if env.SecretRef != nil {
|
||||
if !visitor(env.SecretRef.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, envVar := range container.Env {
|
||||
if envVar.ValueFrom != nil && envVar.ValueFrom.SecretKeyRef != nil {
|
||||
if !visitor(envVar.ValueFrom.SecretKeyRef.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func visitContainerConfigmapNames(container *corev1.Container, visitor Visitor) bool {
|
||||
for _, env := range container.EnvFrom {
|
||||
if env.ConfigMapRef != nil {
|
||||
if !visitor(env.ConfigMapRef.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, envVar := range container.Env {
|
||||
if envVar.ValueFrom != nil && envVar.ValueFrom.ConfigMapKeyRef != nil {
|
||||
if !visitor(envVar.ValueFrom.ConfigMapKeyRef.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetPodFromTemplate generates pod object from a template.
|
||||
func GetPodFromTemplate(template *corev1.PodTemplateSpec, parentObject runtime.Object, controllerRef *metav1.OwnerReference) (*corev1.Pod, error) {
|
||||
desiredLabels := getPodsLabelSet(template)
|
||||
desiredFinalizers := getPodsFinalizers(template)
|
||||
desiredAnnotations := getPodsAnnotationSet(template)
|
||||
accessor, err := meta.Accessor(parentObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parentObject does not have ObjectMeta, %v", err)
|
||||
}
|
||||
prefix := getPodsPrefix(accessor.GetName())
|
||||
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: desiredLabels,
|
||||
Annotations: desiredAnnotations,
|
||||
GenerateName: prefix,
|
||||
Finalizers: desiredFinalizers,
|
||||
Namespace: accessor.GetNamespace(),
|
||||
},
|
||||
}
|
||||
if controllerRef != nil {
|
||||
pod.OwnerReferences = append(pod.OwnerReferences, *controllerRef)
|
||||
}
|
||||
pod.Spec = *template.Spec.DeepCopy()
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
func getPodsLabelSet(template *corev1.PodTemplateSpec) labels.Set {
|
||||
desiredLabels := make(labels.Set)
|
||||
for k, v := range template.Labels {
|
||||
desiredLabels[k] = v
|
||||
}
|
||||
return desiredLabels
|
||||
}
|
||||
|
||||
func getPodsFinalizers(template *corev1.PodTemplateSpec) []string {
|
||||
desiredFinalizers := make([]string, len(template.Finalizers))
|
||||
copy(desiredFinalizers, template.Finalizers)
|
||||
return desiredFinalizers
|
||||
}
|
||||
|
||||
func getPodsAnnotationSet(template *corev1.PodTemplateSpec) labels.Set {
|
||||
desiredAnnotations := make(labels.Set)
|
||||
for k, v := range template.Annotations {
|
||||
desiredAnnotations[k] = v
|
||||
}
|
||||
return desiredAnnotations
|
||||
}
|
||||
|
||||
func getPodsPrefix(controllerName string) string {
|
||||
// use the dash (if the name isn't too long) to make the pod name a bit prettier
|
||||
prefix := fmt.Sprintf("%s-", controllerName)
|
||||
if len(ValidatePodName(prefix, true)) != 0 {
|
||||
prefix = controllerName
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
|
||||
func skipEmptyNames(visitor Visitor) Visitor {
|
||||
return func(name string) bool {
|
||||
if len(name) == 0 {
|
||||
// continue visiting
|
||||
return true
|
||||
}
|
||||
// delegate to visitor
|
||||
return visitor(name)
|
||||
}
|
||||
}
|
||||
|
||||
// VisitPodConfigmapNames invokes the visitor function with the name of every configmap
|
||||
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
|
||||
// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
|
||||
// Returns true if visiting completed, false if visiting was short-circuited.
|
||||
func VisitPodConfigmapNames(pod *corev1.Pod, visitor Visitor) bool {
|
||||
visitor = skipEmptyNames(visitor)
|
||||
VisitContainers(&pod.Spec, AllContainers, func(c *corev1.Container, containerType ContainerType) bool {
|
||||
return visitContainerConfigmapNames(c, visitor)
|
||||
})
|
||||
var source *corev1.VolumeSource
|
||||
for i := range pod.Spec.Volumes {
|
||||
source = &pod.Spec.Volumes[i].VolumeSource
|
||||
switch {
|
||||
case source.Projected != nil:
|
||||
for j := range source.Projected.Sources {
|
||||
if source.Projected.Sources[j].ConfigMap != nil {
|
||||
if !visitor(source.Projected.Sources[j].ConfigMap.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
case source.ConfigMap != nil:
|
||||
if !visitor(source.ConfigMap.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// VisitPodSecretNames invokes the visitor function with the name of every secret
|
||||
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
|
||||
// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
|
||||
|
@ -299,20 +159,68 @@ func VisitPodSecretNames(pod *corev1.Pod, visitor Visitor) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func getSecretNames(pod *corev1.Pod) sets.String {
|
||||
result := sets.NewString()
|
||||
VisitPodSecretNames(pod, func(name string) bool {
|
||||
result.Insert(name)
|
||||
return true
|
||||
})
|
||||
return result
|
||||
func visitContainerSecretNames(container *corev1.Container, visitor Visitor) bool {
|
||||
for _, env := range container.EnvFrom {
|
||||
if env.SecretRef != nil {
|
||||
if !visitor(env.SecretRef.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, envVar := range container.Env {
|
||||
if envVar.ValueFrom != nil && envVar.ValueFrom.SecretKeyRef != nil {
|
||||
if !visitor(envVar.ValueFrom.SecretKeyRef.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getConfigMapNames(pod *corev1.Pod) sets.String {
|
||||
result := sets.NewString()
|
||||
VisitPodConfigmapNames(pod, func(name string) bool {
|
||||
result.Insert(name)
|
||||
return true
|
||||
// VisitPodConfigmapNames invokes the visitor function with the name of every configmap
|
||||
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
|
||||
// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
|
||||
// Returns true if visiting completed, false if visiting was short-circuited.
|
||||
func VisitPodConfigmapNames(pod *corev1.Pod, visitor Visitor) bool {
|
||||
visitor = skipEmptyNames(visitor)
|
||||
VisitContainers(&pod.Spec, AllContainers, func(c *corev1.Container, containerType ContainerType) bool {
|
||||
return visitContainerConfigmapNames(c, visitor)
|
||||
})
|
||||
return result
|
||||
var source *corev1.VolumeSource
|
||||
for i := range pod.Spec.Volumes {
|
||||
source = &pod.Spec.Volumes[i].VolumeSource
|
||||
switch {
|
||||
case source.Projected != nil:
|
||||
for j := range source.Projected.Sources {
|
||||
if source.Projected.Sources[j].ConfigMap != nil {
|
||||
if !visitor(source.Projected.Sources[j].ConfigMap.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
case source.ConfigMap != nil:
|
||||
if !visitor(source.ConfigMap.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func visitContainerConfigmapNames(container *corev1.Container, visitor Visitor) bool {
|
||||
for _, env := range container.EnvFrom {
|
||||
if env.ConfigMapRef != nil {
|
||||
if !visitor(env.ConfigMapRef.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, envVar := range container.Env {
|
||||
if envVar.ValueFrom != nil && envVar.ValueFrom.ConfigMapKeyRef != nil {
|
||||
if !visitor(envVar.ValueFrom.ConfigMapKeyRef.Name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -3,8 +3,6 @@ package objectwatcher
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
@ -19,14 +17,10 @@ import (
|
|||
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
||||
"github.com/karmada-io/karmada/pkg/resourceinterpreter"
|
||||
"github.com/karmada-io/karmada/pkg/util"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
"github.com/karmada-io/karmada/pkg/util/restmapper"
|
||||
)
|
||||
|
||||
const (
|
||||
generationPrefix = "gen:"
|
||||
resourceVersionPrefix = "rv:"
|
||||
)
|
||||
|
||||
// ObjectWatcher manages operations for object dispatched to member clusters.
|
||||
type ObjectWatcher interface {
|
||||
Create(clusterName string, desireObj *unstructured.Unstructured) error
|
||||
|
@ -213,7 +207,7 @@ func (o *objectWatcherImpl) genObjectKey(obj *unstructured.Unstructured) string
|
|||
|
||||
// recordVersion will add or update resource version records
|
||||
func (o *objectWatcherImpl) recordVersion(clusterObj *unstructured.Unstructured, clusterName string) {
|
||||
objVersion := objectVersion(clusterObj)
|
||||
objVersion := lifted.ObjectVersion(clusterObj)
|
||||
objectKey := o.genObjectKey(clusterObj)
|
||||
if o.isClusterVersionRecordExist(clusterName) {
|
||||
o.updateVersionRecord(clusterName, objectKey, objVersion)
|
||||
|
@ -270,55 +264,5 @@ func (o *objectWatcherImpl) NeedsUpdate(clusterName string, desiredObj, clusterO
|
|||
return false, fmt.Errorf("failed to update resource(kind=%s, %s/%s) in cluster %s for the version record does not exist", desiredObj.GetKind(), desiredObj.GetNamespace(), desiredObj.GetName(), clusterName)
|
||||
}
|
||||
|
||||
return objectNeedsUpdate(desiredObj, clusterObj, version), nil
|
||||
}
|
||||
|
||||
/*
|
||||
This code is lifted from the kubefed codebase. It's a list of functions to determine whether the provided cluster
|
||||
object needs to be updated according to the desired object and the recorded version.
|
||||
For reference: https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/util/propagatedversion.go#L30-L59
|
||||
*/
|
||||
|
||||
// objectVersion retrieves the field type-prefixed value used for
|
||||
// determining currency of the given cluster object.
|
||||
func objectVersion(clusterObj *unstructured.Unstructured) string {
|
||||
generation := clusterObj.GetGeneration()
|
||||
if generation != 0 {
|
||||
return fmt.Sprintf("%s%d", generationPrefix, generation)
|
||||
}
|
||||
return fmt.Sprintf("%s%s", resourceVersionPrefix, clusterObj.GetResourceVersion())
|
||||
}
|
||||
|
||||
// objectNeedsUpdate determines whether the 2 objects provided cluster
|
||||
// object needs to be updated according to the desired object and the
|
||||
// recorded version.
|
||||
func objectNeedsUpdate(desiredObj, clusterObj *unstructured.Unstructured, recordedVersion string) bool {
|
||||
targetVersion := objectVersion(clusterObj)
|
||||
|
||||
if recordedVersion != targetVersion {
|
||||
return true
|
||||
}
|
||||
|
||||
// If versions match and the version is sourced from the
|
||||
// generation field, a further check of metadata equivalency is
|
||||
// required.
|
||||
return strings.HasPrefix(targetVersion, generationPrefix) && !objectMetaObjEquivalent(desiredObj, clusterObj)
|
||||
}
|
||||
|
||||
// objectMetaObjEquivalent checks if cluster-independent, user provided data in two given ObjectMeta are equal. If in
|
||||
// the future the ObjectMeta structure is expanded then any field that is not populated
|
||||
// by the api server should be included here.
|
||||
func objectMetaObjEquivalent(a, b metav1.Object) bool {
|
||||
if a.GetName() != b.GetName() {
|
||||
return false
|
||||
}
|
||||
if a.GetNamespace() != b.GetNamespace() {
|
||||
return false
|
||||
}
|
||||
aLabels := a.GetLabels()
|
||||
bLabels := b.GetLabels()
|
||||
if !reflect.DeepEqual(aLabels, bLabels) && (len(aLabels) != 0 || len(bLabels) != 0) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return lifted.ObjectNeedsUpdate(desiredObj, clusterObj, version), nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
|
||||
"github.com/karmada-io/karmada/pkg/util/resourcehelper"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
)
|
||||
|
||||
// Resource is a collection of compute resource.
|
||||
|
@ -40,7 +40,7 @@ func NewResource(rl corev1.ResourceList) *Resource {
|
|||
case corev1.ResourceEphemeralStorage:
|
||||
r.EphemeralStorage += rQuant.Value()
|
||||
default:
|
||||
if resourcehelper.IsScalarResourceName(rName) {
|
||||
if lifted.IsScalarResourceName(rName) {
|
||||
r.AddScalar(rName, rQuant.Value())
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ func (r *Resource) Add(rl corev1.ResourceList) {
|
|||
case corev1.ResourceEphemeralStorage:
|
||||
r.EphemeralStorage += rQuant.Value()
|
||||
default:
|
||||
if resourcehelper.IsScalarResourceName(rName) {
|
||||
if lifted.IsScalarResourceName(rName) {
|
||||
r.AddScalar(rName, rQuant.Value())
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ func (r *Resource) Sub(rl corev1.ResourceList) error {
|
|||
}
|
||||
r.EphemeralStorage -= ephemeralStorage
|
||||
default:
|
||||
if resourcehelper.IsScalarResourceName(rName) {
|
||||
if lifted.IsScalarResourceName(rName) {
|
||||
rScalar, ok := r.ScalarResources[rName]
|
||||
scalar := rQuant.Value()
|
||||
if !ok && scalar > 0 {
|
||||
|
@ -143,7 +143,7 @@ func (r *Resource) SetMaxResource(rl corev1.ResourceList) {
|
|||
r.AllowedPodNumber = pods
|
||||
}
|
||||
default:
|
||||
if resourcehelper.IsScalarResourceName(rName) {
|
||||
if lifted.IsScalarResourceName(rName) {
|
||||
if value := rQuant.Value(); value > r.ScalarResources[rName] {
|
||||
r.SetScalar(rName, value)
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ func (r *Resource) ResourceList() corev1.ResourceList {
|
|||
}
|
||||
for rName, rQuant := range r.ScalarResources {
|
||||
if rQuant > 0 {
|
||||
if resourcehelper.IsHugePageResourceName(rName) {
|
||||
if lifted.IsHugePageResourceName(rName) {
|
||||
result[rName] = *resource.NewQuantity(rQuant, resource.BinarySI)
|
||||
} else {
|
||||
result[rName] = *resource.NewQuantity(rQuant, resource.DecimalSI)
|
||||
|
@ -214,7 +214,7 @@ func (r *Resource) MaxDivided(rl corev1.ResourceList) int64 {
|
|||
res = MinInt64(res, r.EphemeralStorage/ephemeralStorage)
|
||||
}
|
||||
default:
|
||||
if resourcehelper.IsScalarResourceName(rName) {
|
||||
if lifted.IsScalarResourceName(rName) {
|
||||
rScalar := r.ScalarResources[rName]
|
||||
if scalar := rQuant.Value(); scalar > 0 {
|
||||
res = MinInt64(res, rScalar/scalar)
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
package resourcehelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package.
|
||||
// For reference: https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/apis/core/v1/helper/helpers.go,
|
||||
// https://github.com/kubernetes/kubernetes/blob/release-1.22/pkg/scheduler/util/utils.go#L144
|
||||
|
||||
// IsExtendedResourceName returns true if:
|
||||
// 1. the resource name is not in the default namespace;
|
||||
// 2. resource name does not have "requests." prefix,
|
||||
// to avoid confusion with the convention in quota
|
||||
// 3. it satisfies the rules in IsQualifiedName() after converted into quota resource name
|
||||
func IsExtendedResourceName(name corev1.ResourceName) bool {
|
||||
if IsNativeResource(name) || strings.HasPrefix(string(name), corev1.DefaultResourceRequestsPrefix) {
|
||||
return false
|
||||
}
|
||||
// Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name
|
||||
nameForQuota := fmt.Sprintf("%s%s", corev1.DefaultResourceRequestsPrefix, string(name))
|
||||
if errs := validation.IsQualifiedName(string(nameForQuota)); len(errs) != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsPrefixedNativeResource returns true if the resource name is in the
|
||||
// *kubernetes.io/ namespace.
|
||||
func IsPrefixedNativeResource(name corev1.ResourceName) bool {
|
||||
return strings.Contains(string(name), corev1.ResourceDefaultNamespacePrefix)
|
||||
}
|
||||
|
||||
// IsNativeResource returns true if the resource name is in the
|
||||
// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
|
||||
// implicitly in the kubernetes.io/ namespace.
|
||||
func IsNativeResource(name corev1.ResourceName) bool {
|
||||
return !strings.Contains(string(name), "/") ||
|
||||
IsPrefixedNativeResource(name)
|
||||
}
|
||||
|
||||
// IsHugePageResourceName returns true if the resource name has the huge page
|
||||
// resource prefix.
|
||||
func IsHugePageResourceName(name corev1.ResourceName) bool {
|
||||
return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix)
|
||||
}
|
||||
|
||||
// IsAttachableVolumeResourceName returns true when the resource name is prefixed in attachable volume
|
||||
func IsAttachableVolumeResourceName(name corev1.ResourceName) bool {
|
||||
return strings.HasPrefix(string(name), corev1.ResourceAttachableVolumesPrefix)
|
||||
}
|
||||
|
||||
// IsScalarResourceName validates the resource for Extended, Hugepages, Native and AttachableVolume resources
|
||||
func IsScalarResourceName(name corev1.ResourceName) bool {
|
||||
return IsExtendedResourceName(name) || IsHugePageResourceName(name) ||
|
||||
IsPrefixedNativeResource(name) || IsAttachableVolumeResourceName(name)
|
||||
}
|
|
@ -1,16 +1,13 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
|
||||
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
)
|
||||
|
||||
// ResourceMatches tells if the specific resource matches the selector.
|
||||
|
@ -83,7 +80,7 @@ func ClusterMatches(cluster *clusterv1alpha1.Cluster, affinity policyv1alpha1.Cl
|
|||
if affinity.FieldSelector != nil {
|
||||
var matchFields labels.Selector
|
||||
var err error
|
||||
if matchFields, err = nodeSelectorRequirementsAsSelector(affinity.FieldSelector.MatchExpressions); err != nil {
|
||||
if matchFields, err = lifted.NodeSelectorRequirementsAsSelector(affinity.FieldSelector.MatchExpressions); err != nil {
|
||||
return false
|
||||
}
|
||||
clusterFields := extractClusterFields(cluster)
|
||||
|
@ -119,42 +116,6 @@ func ResourceMatchSelectors(resource *unstructured.Unstructured, selectors ...po
|
|||
return false
|
||||
}
|
||||
|
||||
// This code is directly lifted from the Kubernetes codebase.
|
||||
// For reference: https://github.com/kubernetes/kubernetes/blob/release-1.20/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity.go#L193-L225
|
||||
// nodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements
|
||||
// labels.Selector.
|
||||
func nodeSelectorRequirementsAsSelector(nsm []corev1.NodeSelectorRequirement) (labels.Selector, error) {
|
||||
if len(nsm) == 0 {
|
||||
return labels.Nothing(), nil
|
||||
}
|
||||
selector := labels.NewSelector()
|
||||
for _, expr := range nsm {
|
||||
var op selection.Operator
|
||||
switch expr.Operator {
|
||||
case corev1.NodeSelectorOpIn:
|
||||
op = selection.In
|
||||
case corev1.NodeSelectorOpNotIn:
|
||||
op = selection.NotIn
|
||||
case corev1.NodeSelectorOpExists:
|
||||
op = selection.Exists
|
||||
case corev1.NodeSelectorOpDoesNotExist:
|
||||
op = selection.DoesNotExist
|
||||
case corev1.NodeSelectorOpGt:
|
||||
op = selection.GreaterThan
|
||||
case corev1.NodeSelectorOpLt:
|
||||
op = selection.LessThan
|
||||
default:
|
||||
return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator)
|
||||
}
|
||||
r, err := labels.NewRequirement(expr.Key, op, expr.Values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selector = selector.Add(*r)
|
||||
}
|
||||
return selector, nil
|
||||
}
|
||||
|
||||
func extractClusterFields(cluster *clusterv1alpha1.Cluster) labels.Set {
|
||||
clusterFieldsMap := make(labels.Set)
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
clustervalidation "github.com/karmada-io/karmada/pkg/apis/cluster/validation"
|
||||
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
)
|
||||
|
||||
// ValidatingAdmission validates FederatedResourceQuota object when creating/updating.
|
||||
|
@ -149,8 +150,8 @@ func validateResourceList(resourceList corev1.ResourceList, fld *field.Path) fie
|
|||
|
||||
for k, v := range resourceList {
|
||||
resPath := fld.Key(string(k))
|
||||
errs = append(errs, ValidateResourceQuotaResourceName(string(k), resPath)...)
|
||||
errs = append(errs, ValidateResourceQuantityValue(string(k), v, resPath)...)
|
||||
errs = append(errs, lifted.ValidateResourceQuotaResourceName(string(k), resPath)...)
|
||||
errs = append(errs, lifted.ValidateResourceQuantityValue(string(k), v, resPath)...)
|
||||
}
|
||||
|
||||
return errs
|
||||
|
|
Loading…
Reference in New Issue