202 lines
7.3 KiB
Go
202 lines
7.3 KiB
Go
package overridemanager
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
batchv1 "k8s.io/api/batch/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"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/helper"
|
|
"github.com/karmada-io/karmada/pkg/util/imageparser"
|
|
)
|
|
|
|
const (
|
|
pathSplit = "/"
|
|
podSpecPrefix = "/spec"
|
|
podTemplatePrefix = "/spec/template/spec"
|
|
)
|
|
|
|
// 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:
|
|
podObj := &corev1.Pod{}
|
|
if err := helper.ConvertToTypedObject(rawObj, podObj); err != nil {
|
|
return nil, fmt.Errorf("failed to convert Pod from unstructured object: %v", err)
|
|
}
|
|
return extractPatchesBy(podObj.Spec, podSpecPrefix, imageOverrider)
|
|
case util.ReplicaSetKind:
|
|
replicaSetObj := &appsv1.ReplicaSet{}
|
|
if err := helper.ConvertToTypedObject(rawObj, replicaSetObj); err != nil {
|
|
return nil, fmt.Errorf("failed to convert ReplicaSet from unstructured object: %v", err)
|
|
}
|
|
return extractPatchesBy(replicaSetObj.Spec.Template.Spec, podTemplatePrefix, imageOverrider)
|
|
case util.DeploymentKind:
|
|
deploymentObj := &appsv1.Deployment{}
|
|
if err := helper.ConvertToTypedObject(rawObj, deploymentObj); err != nil {
|
|
return nil, fmt.Errorf("failed to convert Deployment from unstructured object: %v", err)
|
|
}
|
|
return extractPatchesBy(deploymentObj.Spec.Template.Spec, podTemplatePrefix, imageOverrider)
|
|
case util.DaemonSetKind:
|
|
daemonSetObj := &appsv1.DaemonSet{}
|
|
if err := helper.ConvertToTypedObject(rawObj, daemonSetObj); err != nil {
|
|
return nil, fmt.Errorf("failed to convert DaemonSet from unstructured object: %v", err)
|
|
}
|
|
return extractPatchesBy(daemonSetObj.Spec.Template.Spec, podTemplatePrefix, imageOverrider)
|
|
case util.StatefulSetKind:
|
|
statefulSetObj := &appsv1.StatefulSet{}
|
|
if err := helper.ConvertToTypedObject(rawObj, statefulSetObj); err != nil {
|
|
return nil, fmt.Errorf("failed to convert StatefulSet from unstructured object: %v", err)
|
|
}
|
|
return extractPatchesBy(statefulSetObj.Spec.Template.Spec, podTemplatePrefix, imageOverrider)
|
|
case util.JobKind:
|
|
jobObj := &batchv1.Job{}
|
|
if err := helper.ConvertToTypedObject(rawObj, jobObj); err != nil {
|
|
return nil, fmt.Errorf("failed to convert Job from unstructured object: %v", err)
|
|
}
|
|
return extractPatchesBy(jobObj.Spec.Template.Spec, podTemplatePrefix, imageOverrider)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func extractPatchesBy(podSpec corev1.PodSpec, prefixPath string, imageOverrider *policyv1alpha1.ImageOverrider) ([]overrideOption, error) {
|
|
patches := make([]overrideOption, 0)
|
|
|
|
for containerIndex, container := range podSpec.Containers {
|
|
patch, err := acquireOverrideOption(spliceImagePath(prefixPath, containerIndex), container.Image, imageOverrider)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
patches = append(patches, patch)
|
|
}
|
|
|
|
return patches, nil
|
|
}
|
|
|
|
func spliceImagePath(prefixPath string, containerIndex int) string {
|
|
return fmt.Sprintf("%s/containers/%d/image", prefixPath, containerIndex)
|
|
}
|
|
|
|
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)
|
|
}
|