ImageOverrider Implementation
Signed-off-by: changzhen <changzhen5@huawei.com>
This commit is contained in:
parent
06e3bb360b
commit
fa9a2ea662
|
@ -73,10 +73,11 @@ spec:
|
|||
automatically detect image fields if the resource type
|
||||
is Pod, ReplicaSet, Deployment or StatefulSet by following
|
||||
rule: - Pod: spec/containers/<N>/image - ReplicaSet:
|
||||
spec/template/spec/<N>/image - Deployment: spec/template/spec/<N>/image
|
||||
\ - StatefulSet: spec/template/spec/<N>/image In addition,
|
||||
all images will be processed if the resource object has
|
||||
more than one containers. \n If not nil, only images matches
|
||||
spec/template/spec/containers/<N>/image - Deployment:
|
||||
spec/template/spec/containers/<N>/image - StatefulSet:
|
||||
spec/template/spec/containers/<N>/image In addition, all
|
||||
images will be processed if the resource object has more
|
||||
than one containers. \n If not nil, only images matches
|
||||
the filters will be processed."
|
||||
properties:
|
||||
path:
|
||||
|
|
|
@ -73,10 +73,11 @@ spec:
|
|||
automatically detect image fields if the resource type
|
||||
is Pod, ReplicaSet, Deployment or StatefulSet by following
|
||||
rule: - Pod: spec/containers/<N>/image - ReplicaSet:
|
||||
spec/template/spec/<N>/image - Deployment: spec/template/spec/<N>/image
|
||||
\ - StatefulSet: spec/template/spec/<N>/image In addition,
|
||||
all images will be processed if the resource object has
|
||||
more than one containers. \n If not nil, only images matches
|
||||
spec/template/spec/containers/<N>/image - Deployment:
|
||||
spec/template/spec/containers/<N>/image - StatefulSet:
|
||||
spec/template/spec/containers/<N>/image In addition, all
|
||||
images will be processed if the resource object has more
|
||||
than one containers. \n If not nil, only images matches
|
||||
the filters will be processed."
|
||||
properties:
|
||||
path:
|
||||
|
|
|
@ -57,9 +57,9 @@ type ImageOverrider struct {
|
|||
// Defaults to nil, in that case, the system will automatically detect image fields if the resource type is
|
||||
// Pod, ReplicaSet, Deployment or StatefulSet by following rule:
|
||||
// - Pod: spec/containers/<N>/image
|
||||
// - ReplicaSet: spec/template/spec/<N>/image
|
||||
// - Deployment: spec/template/spec/<N>/image
|
||||
// - StatefulSet: spec/template/spec/<N>/image
|
||||
// - ReplicaSet: spec/template/spec/containers/<N>/image
|
||||
// - Deployment: spec/template/spec/containers/<N>/image
|
||||
// - StatefulSet: spec/template/spec/containers/<N>/image
|
||||
// In addition, all images will be processed if the resource object has more than one containers.
|
||||
//
|
||||
// If not nil, only images matches the filters will be processed.
|
||||
|
|
|
@ -63,3 +63,19 @@ const (
|
|||
// ZoneField indicates the 'zone' field of a cluster
|
||||
ZoneField = "zone"
|
||||
)
|
||||
|
||||
// Define resource kind.
|
||||
const (
|
||||
// DeploymentKind indicates the target resource is a deployment
|
||||
DeploymentKind = "Deployment"
|
||||
// ServiceKind indicates the target resource is a service
|
||||
ServiceKind = "Service"
|
||||
// PodKind indicates the target resource is a pod
|
||||
PodKind = "Pod"
|
||||
// ServiceAccountKind indicates the target resource is a serviceaccount
|
||||
ServiceAccountKind = "ServiceAccount"
|
||||
// ReplicaSetKind indicates the target resource is a replicaset
|
||||
ReplicaSetKind = "ReplicaSet"
|
||||
// StatefulSetKind indicates the target resource is a statefulset
|
||||
StatefulSetKind = "StatefulSet"
|
||||
)
|
||||
|
|
|
@ -860,7 +860,7 @@ func (d *ResourceDetector) ReconcileResourceBinding(key util.QueueKey) error {
|
|||
|
||||
klog.Infof("Reconciling resource binding(%s/%s)", binding.Namespace, binding.Name)
|
||||
switch binding.Spec.Resource.Kind {
|
||||
case helper.DeploymentKind:
|
||||
case util.DeploymentKind:
|
||||
return d.AggregateDeploymentStatus(binding.Spec.Resource, binding.Status.AggregatedStatus)
|
||||
default:
|
||||
// Unsupported resource type.
|
||||
|
@ -925,7 +925,7 @@ func (d *ResourceDetector) ReconcileClusterResourceBinding(key util.QueueKey) er
|
|||
|
||||
klog.Infof("Reconciling cluster resource binding(%s)", binding.Name)
|
||||
switch binding.Spec.Resource.Kind {
|
||||
case helper.DeploymentKind:
|
||||
case util.DeploymentKind:
|
||||
return d.AggregateDeploymentStatus(binding.Spec.Resource, binding.Status.AggregatedStatus)
|
||||
default:
|
||||
// Unsupported resource type.
|
||||
|
@ -992,7 +992,7 @@ func (d *ResourceDetector) AggregateDeploymentStatus(objRef workv1alpha1.ObjectR
|
|||
// Note: Only limited resource type supported.
|
||||
func (d *ResourceDetector) CleanupResourceTemplateStatus(objRef workv1alpha1.ObjectReference) error {
|
||||
switch objRef.Kind {
|
||||
case helper.DeploymentKind:
|
||||
case util.DeploymentKind:
|
||||
return d.CleanupDeploymentStatus(objRef)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,8 +30,6 @@ var resourceBindingKind = v1alpha1.SchemeGroupVersion.WithKind("ResourceBinding"
|
|||
var clusterResourceBindingKind = v1alpha1.SchemeGroupVersion.WithKind("ClusterResourceBinding")
|
||||
|
||||
const (
|
||||
// DeploymentKind indicates the target resource is a deployment
|
||||
DeploymentKind = "Deployment"
|
||||
// SpecField indicates the 'spec' field of a deployment
|
||||
SpecField = "spec"
|
||||
// ReplicasField indicates the 'replicas' field of a deployment
|
||||
|
@ -196,7 +194,7 @@ func EnsureWork(c client.Client, workload *unstructured.Unstructured, clusterNam
|
|||
workLabel[util.ClusterResourceBindingLabel] = binding.GetName()
|
||||
}
|
||||
|
||||
if clonedWorkload.GetKind() == DeploymentKind && referenceRSP != nil {
|
||||
if clonedWorkload.GetKind() == util.DeploymentKind && referenceRSP != nil {
|
||||
err = applyReplicaSchedulingPolicy(clonedWorkload, desireReplicaInfos[clusterName])
|
||||
if err != nil {
|
||||
klog.Errorf("failed to apply ReplicaSchedulingPolicy for %s/%s/%s in cluster %s, err is: %v",
|
||||
|
@ -290,7 +288,7 @@ func calculateReplicasIfNeeded(c client.Client, workload *unstructured.Unstructu
|
|||
var referenceRSP *v1alpha1.ReplicaSchedulingPolicy
|
||||
var desireReplicaInfos = make(map[string]int64)
|
||||
|
||||
if workload.GetKind() == DeploymentKind {
|
||||
if workload.GetKind() == util.DeploymentKind {
|
||||
referenceRSP, err = matchReplicaSchedulingPolicy(c, workload)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
|
@ -121,11 +121,13 @@ func (c *Components) TagOrDigest() string {
|
|||
func (c *Components) SetTagOrDigest(input string) {
|
||||
if anchoredTagRegexp.MatchString(input) {
|
||||
c.SetTag(input)
|
||||
c.RemoveDigest()
|
||||
return
|
||||
}
|
||||
|
||||
if anchoredDigestRegexp.MatchString(input) {
|
||||
c.SetDigest(input)
|
||||
c.RemoveTag()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/karmada-io/karmada/pkg/util"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -13,12 +15,6 @@ For reference: https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/contro
|
|||
*/
|
||||
|
||||
const (
|
||||
// ServiceKind indicates the target resource is a service
|
||||
ServiceKind = "Service"
|
||||
// PodKind indicates the target resource is a pod
|
||||
PodKind = "Pod"
|
||||
// ServiceAccountKind indicates the target resource is a serviceaccount
|
||||
ServiceAccountKind = "ServiceAccount"
|
||||
// SecretsField indicates the 'secrets' field of a service account
|
||||
SecretsField = "secrets"
|
||||
)
|
||||
|
@ -36,13 +32,13 @@ func RetainClusterFields(desiredObj, clusterObj *unstructured.Unstructured) erro
|
|||
desiredObj.SetFinalizers(clusterObj.GetFinalizers())
|
||||
desiredObj.SetAnnotations(clusterObj.GetAnnotations())
|
||||
|
||||
if targetKind == PodKind {
|
||||
if targetKind == util.PodKind {
|
||||
return retainPodFields(desiredObj, clusterObj)
|
||||
}
|
||||
if targetKind == ServiceKind {
|
||||
if targetKind == util.ServiceKind {
|
||||
return retainServiceFields(desiredObj, clusterObj)
|
||||
}
|
||||
if targetKind == ServiceAccountKind {
|
||||
if targetKind == util.ServiceAccountKind {
|
||||
return retainServiceAccountFields(desiredObj, clusterObj)
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
package overridemanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
||||
"github.com/karmada-io/karmada/pkg/util"
|
||||
"github.com/karmada-io/karmada/pkg/util/imageparser"
|
||||
)
|
||||
|
||||
const (
|
||||
pathSplit = "/"
|
||||
imageString = "image"
|
||||
)
|
||||
|
||||
// buildPatches parse JSON patches from resource object by imageOverriders
|
||||
func buildPatches(rawObj *unstructured.Unstructured, imageOverrider *policyv1alpha1.ImageOverrider) ([]overrideOption, error) {
|
||||
if imageOverrider.Predicate == nil {
|
||||
return buildPatchesWithEmptyPredicate(rawObj, imageOverrider)
|
||||
}
|
||||
|
||||
return buildPatchesWithPredicate(rawObj, imageOverrider)
|
||||
}
|
||||
|
||||
func buildPatchesWithEmptyPredicate(rawObj *unstructured.Unstructured, imageOverrider *policyv1alpha1.ImageOverrider) ([]overrideOption, error) {
|
||||
switch rawObj.GetKind() {
|
||||
case util.PodKind:
|
||||
return buildPatchesWithPath("spec/containers", rawObj, imageOverrider)
|
||||
case util.ReplicaSetKind:
|
||||
fallthrough
|
||||
case util.DeploymentKind:
|
||||
fallthrough
|
||||
case util.StatefulSetKind:
|
||||
return buildPatchesWithPath("spec/template/spec/containers", rawObj, imageOverrider)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func buildPatchesWithPath(specContainersPath string, rawObj *unstructured.Unstructured, imageOverrider *policyv1alpha1.ImageOverrider) ([]overrideOption, error) {
|
||||
patches := make([]overrideOption, 0)
|
||||
|
||||
containers, ok, err := unstructured.NestedSlice(rawObj.Object, strings.Split(specContainersPath, pathSplit)...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieves path(%s) from rawObj, error: %v", specContainersPath, err)
|
||||
}
|
||||
if !ok || len(containers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for index := range containers {
|
||||
imagePath := fmt.Sprintf("/%s/%d/image", specContainersPath, index)
|
||||
imageValue := containers[index].(map[string]interface{})[imageString].(string)
|
||||
patch, err := acquireOverrideOption(imagePath, imageValue, imageOverrider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patches = append(patches, patch)
|
||||
}
|
||||
|
||||
return patches, nil
|
||||
}
|
||||
|
||||
func buildPatchesWithPredicate(rawObj *unstructured.Unstructured, imageOverrider *policyv1alpha1.ImageOverrider) ([]overrideOption, error) {
|
||||
patches := make([]overrideOption, 0)
|
||||
|
||||
imageValue, err := obtainImageValue(rawObj, imageOverrider.Predicate.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to obtain imageValue with predicate path(%s), error: %v", imageOverrider.Predicate.Path, err)
|
||||
}
|
||||
|
||||
patch, err := acquireOverrideOption(imageOverrider.Predicate.Path, imageValue, imageOverrider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patches = append(patches, patch)
|
||||
return patches, nil
|
||||
}
|
||||
|
||||
func obtainImageValue(rawObj *unstructured.Unstructured, predicatePath string) (string, error) {
|
||||
pathSegments := strings.Split(strings.Trim(predicatePath, pathSplit), pathSplit)
|
||||
imageValue := ""
|
||||
currentObj := rawObj.Object
|
||||
ok := false
|
||||
for index := 0; index < len(pathSegments)-1; index++ {
|
||||
switch currentObj[pathSegments[index]].(type) {
|
||||
case map[string]interface{}:
|
||||
currentObj = currentObj[pathSegments[index]].(map[string]interface{})
|
||||
case []interface{}:
|
||||
tmpSlice := currentObj[pathSegments[index]].([]interface{})
|
||||
sliceIndex, err := strconv.ParseInt(pathSegments[index+1], 10, 32)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("path(%s) of rawObj's is not number", pathSegments[index+1])
|
||||
}
|
||||
currentObj = tmpSlice[sliceIndex].(map[string]interface{})
|
||||
index++
|
||||
default:
|
||||
return "", fmt.Errorf("path(%s) of rawObj's type is not map[string]interface{} and []interface{}", pathSegments[index])
|
||||
}
|
||||
}
|
||||
|
||||
imageValue, ok = currentObj[pathSegments[len(pathSegments)-1]].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("failed to convert path(%s) to string", pathSegments[len(pathSegments)-1])
|
||||
}
|
||||
|
||||
return imageValue, nil
|
||||
}
|
||||
|
||||
func acquireOverrideOption(imagePath, curImage string, imageOverrider *policyv1alpha1.ImageOverrider) (overrideOption, error) {
|
||||
if !strings.HasPrefix(imagePath, pathSplit) {
|
||||
return overrideOption{}, fmt.Errorf("imagePath should be start with / character")
|
||||
}
|
||||
|
||||
newImage, err := overrideImage(curImage, imageOverrider)
|
||||
if err != nil {
|
||||
return overrideOption{}, err
|
||||
}
|
||||
|
||||
return overrideOption{
|
||||
Op: string(policyv1alpha1.OverriderOpReplace),
|
||||
Path: imagePath,
|
||||
Value: newImage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func overrideImage(curImage string, imageOverrider *policyv1alpha1.ImageOverrider) (string, error) {
|
||||
imageComponent, err := imageparser.Parse(curImage)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse image value(%s), error: %v", curImage, err)
|
||||
}
|
||||
|
||||
switch imageOverrider.Component {
|
||||
case policyv1alpha1.Registry:
|
||||
switch imageOverrider.Operator {
|
||||
case policyv1alpha1.OverriderOpAdd:
|
||||
imageComponent.SetHostname(imageComponent.Hostname() + imageOverrider.Value)
|
||||
case policyv1alpha1.OverriderOpReplace:
|
||||
imageComponent.SetHostname(imageOverrider.Value)
|
||||
case policyv1alpha1.OverriderOpRemove:
|
||||
imageComponent.RemoveHostname()
|
||||
}
|
||||
return imageComponent.String(), nil
|
||||
case policyv1alpha1.Repository:
|
||||
switch imageOverrider.Operator {
|
||||
case policyv1alpha1.OverriderOpAdd:
|
||||
imageComponent.SetRepository(imageComponent.Repository() + imageOverrider.Value)
|
||||
case policyv1alpha1.OverriderOpReplace:
|
||||
imageComponent.SetRepository(imageOverrider.Value)
|
||||
case policyv1alpha1.OverriderOpRemove:
|
||||
imageComponent.RemoveRepository()
|
||||
}
|
||||
return imageComponent.String(), nil
|
||||
case policyv1alpha1.Tag:
|
||||
switch imageOverrider.Operator {
|
||||
case policyv1alpha1.OverriderOpAdd:
|
||||
imageComponent.SetTagOrDigest(imageComponent.TagOrDigest() + imageOverrider.Value)
|
||||
case policyv1alpha1.OverriderOpReplace:
|
||||
imageComponent.SetTagOrDigest(imageOverrider.Value)
|
||||
case policyv1alpha1.OverriderOpRemove:
|
||||
imageComponent.RemoveTagOrDigest()
|
||||
}
|
||||
return imageComponent.String(), nil
|
||||
}
|
||||
|
||||
// should never reach to here
|
||||
return "", fmt.Errorf("unsupported image component(%s)", imageOverrider.Component)
|
||||
}
|
|
@ -26,6 +26,7 @@ type OverrideManager interface {
|
|||
ApplyOverridePolicies(rawObj *unstructured.Unstructured, cluster string) (appliedClusterPolicies *AppliedOverrides, appliedNamespacedPolicies *AppliedOverrides, err error)
|
||||
}
|
||||
|
||||
// overrideOption define the JSONPatch operator
|
||||
type overrideOption struct {
|
||||
Op string `json:"op"`
|
||||
Path string `json:"path"`
|
||||
|
@ -96,7 +97,8 @@ func (o *overrideManagerImpl) applyClusterOverrides(rawObj *unstructured.Unstruc
|
|||
|
||||
appliedList := &AppliedOverrides{}
|
||||
for _, p := range matchingPolicies {
|
||||
if err := applyJSONPatch(rawObj, parseJSONPatch(p.Spec.Overriders.Plaintext)); err != nil {
|
||||
if err := applyPolicyOverriders(rawObj, p.Spec.Overriders); err != nil {
|
||||
klog.Errorf("Failed to apply cluster overrides(%s) for resource(%s/%s), error: %v", p.Name, rawObj.GetNamespace(), rawObj.GetName(), err)
|
||||
return nil, err
|
||||
}
|
||||
klog.V(2).Infof("Applied cluster overrides(%s) for %s/%s", p.Name, rawObj.GetNamespace(), rawObj.GetName())
|
||||
|
@ -121,16 +123,17 @@ func (o *overrideManagerImpl) applyNamespacedOverrides(rawObj *unstructured.Unst
|
|||
|
||||
matchingPolicies := o.getMatchingOverridePolicies(policyList.Items, rawObj, cluster)
|
||||
if len(matchingPolicies) == 0 {
|
||||
klog.V(2).Infof("No override policy for resource: %s/%s", rawObj.GetNamespace(), rawObj.GetName())
|
||||
klog.V(2).Infof("No override policy for resource(%s/%s)", rawObj.GetNamespace(), rawObj.GetName())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
appliedList := &AppliedOverrides{}
|
||||
for _, p := range matchingPolicies {
|
||||
if err := applyJSONPatch(rawObj, parseJSONPatch(p.Spec.Overriders.Plaintext)); err != nil {
|
||||
if err := applyPolicyOverriders(rawObj, p.Spec.Overriders); err != nil {
|
||||
klog.Errorf("Failed to apply overrides(%s/%s) for resource(%s/%s), error: %v", p.Namespace, p.Name, rawObj.GetNamespace(), rawObj.GetName(), err)
|
||||
return nil, err
|
||||
}
|
||||
klog.V(2).Infof("Applied overrides(%s/%s) for %s/%s", p.Namespace, p.Name, rawObj.GetNamespace(), rawObj.GetName())
|
||||
klog.V(2).Infof("Applied overrides(%s/%s) for resource(%s/%s)", p.Namespace, p.Name, rawObj.GetNamespace(), rawObj.GetName())
|
||||
appliedList.Add(p.Name, p.Spec.Overriders)
|
||||
}
|
||||
|
||||
|
@ -207,18 +210,6 @@ func (o *overrideManagerImpl) getMatchingOverridePolicies(policies []policyv1alp
|
|||
return clusterMatchingPolicies
|
||||
}
|
||||
|
||||
func parseJSONPatch(overriders []policyv1alpha1.PlaintextOverrider) []overrideOption {
|
||||
patches := make([]overrideOption, 0, len(overriders))
|
||||
for i := range overriders {
|
||||
patches = append(patches, overrideOption{
|
||||
Op: string(overriders[i].Operator),
|
||||
Path: overriders[i].Path,
|
||||
Value: overriders[i].Value,
|
||||
})
|
||||
}
|
||||
return patches
|
||||
}
|
||||
|
||||
// applyJSONPatch applies the override on to the given unstructured object.
|
||||
func applyJSONPatch(obj *unstructured.Unstructured, overrides []overrideOption) error {
|
||||
jsonPatchBytes, err := json.Marshal(overrides)
|
||||
|
@ -244,3 +235,41 @@ func applyJSONPatch(obj *unstructured.Unstructured, overrides []overrideOption)
|
|||
err = obj.UnmarshalJSON(patchedObjectJSONBytes)
|
||||
return err
|
||||
}
|
||||
|
||||
// applyPolicyOverriders applies OverridePolicy/ClusterOverridePolicy overriders to target object
|
||||
func applyPolicyOverriders(rawObj *unstructured.Unstructured, overriders policyv1alpha1.Overriders) error {
|
||||
err := applyImageOverriders(rawObj, overriders.ImageOverrider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return applyJSONPatch(rawObj, parseJSONPatchesByPlaintext(overriders.Plaintext))
|
||||
}
|
||||
|
||||
func applyImageOverriders(rawObj *unstructured.Unstructured, imageOverriders []policyv1alpha1.ImageOverrider) error {
|
||||
for index := range imageOverriders {
|
||||
patches, err := buildPatches(rawObj, &imageOverriders[index])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Parsed JSON patches by imageOverriders(%+v): %+v", imageOverriders[index], patches)
|
||||
if err = applyJSONPatch(rawObj, patches); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseJSONPatchesByPlaintext(overriders []policyv1alpha1.PlaintextOverrider) []overrideOption {
|
||||
patches := make([]overrideOption, 0, len(overriders))
|
||||
for i := range overriders {
|
||||
patches = append(patches, overrideOption{
|
||||
Op: string(overriders[i].Operator),
|
||||
Path: overriders[i].Path,
|
||||
Value: overriders[i].Value,
|
||||
})
|
||||
}
|
||||
return patches
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue