karmada/pkg/util/overridemanager/imageoverride.go

174 lines
5.8 KiB
Go

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