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 }