github.com/oam-dev/kubevela@v1.9.11/pkg/policy/envbinding/patch.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package envbinding
    18  
    19  import (
    20  	"encoding/json"
    21  	"regexp"
    22  	"strings"
    23  
    24  	"github.com/imdario/mergo"
    25  	"github.com/pkg/errors"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  
    28  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    29  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
    30  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    31  	"github.com/oam-dev/kubevela/pkg/oam/util"
    32  	"github.com/oam-dev/kubevela/pkg/policy/utils"
    33  	errors2 "github.com/oam-dev/kubevela/pkg/utils/errors"
    34  )
    35  
    36  // MergeRawExtension merge two raw extension
    37  func MergeRawExtension(base *runtime.RawExtension, patch *runtime.RawExtension) (*runtime.RawExtension, error) {
    38  	patchParameter, err := util.RawExtension2Map(patch)
    39  	if err != nil {
    40  		return nil, errors.Wrapf(err, "failed to convert patch parameters to map")
    41  	}
    42  	baseParameter, err := util.RawExtension2Map(base)
    43  	if err != nil {
    44  		return nil, errors.Wrapf(err, "failed to convert base parameters to map")
    45  	}
    46  	if baseParameter == nil {
    47  		baseParameter = make(map[string]interface{})
    48  	}
    49  	err = mergo.Merge(&baseParameter, patchParameter, mergo.WithOverride)
    50  	if err != nil {
    51  		return nil, errors.Wrapf(err, "failed to do merge with override")
    52  	}
    53  	bs, err := json.Marshal(baseParameter)
    54  	if err != nil {
    55  		return nil, errors.Wrapf(err, "failed to marshal merged properties")
    56  	}
    57  	return &runtime.RawExtension{Raw: bs}, nil
    58  }
    59  
    60  // MergeComponent merge two component, it will first merge their properties and then merge their traits
    61  func MergeComponent(base *common.ApplicationComponent, patch *v1alpha1.EnvComponentPatch) (*common.ApplicationComponent, error) {
    62  	newComponent := base.DeepCopy()
    63  	var err error
    64  
    65  	// merge component properties
    66  	newComponent.Properties, err = MergeRawExtension(base.Properties, patch.Properties)
    67  	if err != nil {
    68  		return nil, errors.Wrapf(err, "failed to merge component properties")
    69  	}
    70  
    71  	// merge component external revision
    72  	if patch.ExternalRevision != "" {
    73  		newComponent.ExternalRevision = patch.ExternalRevision
    74  	}
    75  
    76  	// prepare traits
    77  	traitMaps := map[string]*common.ApplicationTrait{}
    78  	var traitOrders []string
    79  	for _, trait := range base.Traits {
    80  		traitMaps[trait.Type] = trait.DeepCopy()
    81  		traitOrders = append(traitOrders, trait.Type)
    82  	}
    83  
    84  	// patch traits
    85  	var errs errors2.ErrorList
    86  	for _, trait := range patch.Traits {
    87  		if baseTrait, exists := traitMaps[trait.Type]; exists {
    88  			if trait.Disable {
    89  				delete(traitMaps, trait.Type)
    90  				continue
    91  			}
    92  			baseTrait.Properties, err = MergeRawExtension(baseTrait.Properties, trait.Properties)
    93  			if err != nil {
    94  				errs = append(errs, errors.Wrapf(err, "failed to merge trait %s", trait.Type))
    95  			}
    96  		} else {
    97  			if trait.Disable {
    98  				continue
    99  			}
   100  			traitMaps[trait.Type] = trait.ToApplicationTrait()
   101  			traitOrders = append(traitOrders, trait.Type)
   102  		}
   103  	}
   104  	if errs.HasError() {
   105  		return nil, errors.Wrapf(err, "failed to merge component traits")
   106  	}
   107  
   108  	// fill in traits
   109  	newComponent.Traits = []common.ApplicationTrait{}
   110  	for _, traitType := range traitOrders {
   111  		if _, exists := traitMaps[traitType]; exists {
   112  			newComponent.Traits = append(newComponent.Traits, *traitMaps[traitType])
   113  		}
   114  	}
   115  	return newComponent, nil
   116  }
   117  
   118  // PatchApplication patch base application with patch and selector
   119  func PatchApplication(base *v1beta1.Application, patch *v1alpha1.EnvPatch, selector *v1alpha1.EnvSelector) (*v1beta1.Application, error) {
   120  	newApp := base.DeepCopy()
   121  	var err error
   122  	var compSelector []string
   123  	if selector != nil {
   124  		compSelector = selector.Components
   125  	}
   126  	var compPatch []v1alpha1.EnvComponentPatch
   127  	if patch != nil {
   128  		compPatch = patch.Components
   129  	}
   130  	newApp.Spec.Components, err = PatchComponents(base.Spec.Components, compPatch, compSelector)
   131  	return newApp, err
   132  }
   133  
   134  // PatchComponents patch base components with patch and selector
   135  func PatchComponents(baseComponents []common.ApplicationComponent, patchComponents []v1alpha1.EnvComponentPatch, selector []string) ([]common.ApplicationComponent, error) {
   136  	// init components
   137  	compMaps := map[string]*common.ApplicationComponent{}
   138  	var compOrders []string
   139  	for _, comp := range baseComponents {
   140  		compMaps[comp.Name] = comp.DeepCopy()
   141  		compOrders = append(compOrders, comp.Name)
   142  	}
   143  
   144  	// patch components
   145  	var errs errors2.ErrorList
   146  	var err error
   147  	for _, comp := range patchComponents {
   148  		if comp.Name == "" {
   149  			// when no component name specified in the patch
   150  			// 1. if no type name specified in the patch, it will merge all components
   151  			// 2. if type name specified, it will merge components with the specified type
   152  			for compName, baseComp := range compMaps {
   153  				if comp.Type == "" || comp.Type == baseComp.Type {
   154  					compMaps[compName], err = MergeComponent(baseComp, comp.DeepCopy())
   155  					if err != nil {
   156  						errs = append(errs, errors.Wrapf(err, "failed to merge component %s", compName))
   157  					}
   158  				}
   159  			}
   160  		} else {
   161  			// when component name (pattern) specified in the patch, it will find the component with the matched name
   162  			// 1. if the component type is not specified in the patch, the matched component will be merged with the patch
   163  			// 2. if the matched component uses the same type, the matched component will be merged with the patch
   164  			// 3. if the matched component uses a different type, the matched component will be overridden by the patch
   165  			// 4. if no component matches, and the component name is a valid kubernetes name, a new component will be added
   166  			addComponent := regexp.MustCompile("[a-z]([a-z-]{0,61}[a-z])?").MatchString(comp.Name)
   167  			if re, err := regexp.Compile(strings.ReplaceAll(comp.Name, "*", ".*")); err == nil {
   168  				for compName, baseComp := range compMaps {
   169  					if re.MatchString(compName) {
   170  						addComponent = false
   171  						if baseComp.Type != comp.Type && comp.Type != "" {
   172  							compMaps[compName] = comp.ToApplicationComponent()
   173  						} else {
   174  							compMaps[compName], err = MergeComponent(baseComp, comp.DeepCopy())
   175  							if err != nil {
   176  								errs = append(errs, errors.Wrapf(err, "failed to merge component %s", comp.Name))
   177  							}
   178  						}
   179  					}
   180  				}
   181  			}
   182  			if addComponent {
   183  				compMaps[comp.Name] = comp.ToApplicationComponent()
   184  				compOrders = append(compOrders, comp.Name)
   185  			}
   186  		}
   187  	}
   188  	if errs.HasError() {
   189  		return nil, errors.Wrapf(err, "failed to merge application components")
   190  	}
   191  
   192  	// if selector is enabled, filter
   193  	compOrders = utils.FilterComponents(compOrders, selector)
   194  
   195  	// fill in new application
   196  	newComponents := []common.ApplicationComponent{}
   197  	for _, compName := range compOrders {
   198  		newComponents = append(newComponents, *compMaps[compName])
   199  	}
   200  	return newComponents, nil
   201  }