ImageOverrider Implementation

Signed-off-by: changzhen <changzhen5@huawei.com>
This commit is contained in:
changzhen 2021-05-24 10:47:01 +08:00 committed by Hongcai Ren
parent 06e3bb360b
commit fa9a2ea662
10 changed files with 259 additions and 43 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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.

View File

@ -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"
)

View File

@ -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)
}

View File

@ -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

View File

@ -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()
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}