github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/internal/fnruntime/utils.go (about) 1 // Copyright 2021 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fnruntime 16 17 import ( 18 "bytes" 19 "fmt" 20 "path/filepath" 21 22 "github.com/GoogleContainerTools/kpt/internal/types" 23 fnresult "github.com/GoogleContainerTools/kpt/pkg/api/fnresult/v1" 24 kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" 25 "sigs.k8s.io/kustomize/kyaml/filesys" 26 "sigs.k8s.io/kustomize/kyaml/yaml" 27 ) 28 29 // ResourceIDAnnotation is used to uniquely identify the resource during round trip 30 // to and from a function execution. This annotation is meant to be consumed by 31 // kpt during round trip and should be deleted after that 32 const ResourceIDAnnotation = "internal.config.k8s.io/kpt-resource-id" 33 34 // SaveResults saves results gathered from running the pipeline at specified dir in the input FileSystem. 35 func SaveResults(fsys filesys.FileSystem, resultsDir string, fnResults *fnresult.ResultList) (string, error) { 36 if resultsDir == "" { 37 return "", nil 38 } 39 filePath := filepath.Join(resultsDir, "results.yaml") 40 out := &bytes.Buffer{} 41 42 // use kyaml encoder to ensure consistent indentation 43 e := yaml.NewEncoderWithOptions(out, &yaml.EncoderOptions{SeqIndent: yaml.WideSequenceStyle}) 44 err := e.Encode(fnResults) 45 if err != nil { 46 return "", err 47 } 48 49 err = fsys.WriteFile(filePath, out.Bytes()) 50 if err != nil { 51 return "", err 52 } 53 54 return filePath, nil 55 } 56 57 // MergeWithInput merges the transformed output with input resources 58 // input: all input resources, selectedInput: selected input resources 59 // output: output resources as the result of function on selectedInput resources 60 // for input: A,B,C,D; selectedInput: A,B; output: A,E(A transformed, B Deleted, E Added) 61 // the result should be A,C,D,E 62 // resources are identified by kpt-resource-id annotation 63 func MergeWithInput(output, selectedInput, input []*yaml.RNode) []*yaml.RNode { 64 var result []*yaml.RNode 65 for i := range input { 66 presentInSelectedInput := presentIn(input[i], selectedInput) 67 presentInOutput := presentIn(input[i], output) 68 if !presentInSelectedInput { 69 // this resource is untouched 70 result = append(result, input[i]) 71 continue 72 } 73 74 if presentInOutput { 75 // this resource modified by function, so replace it from the output 76 result = append(result, nodeWithResourceID(input[i].GetAnnotations()[ResourceIDAnnotation], output)) 77 } 78 // if presentInSelectedInput and !presentInOutput the resource is deleted, so ignore it 79 } 80 81 // add function generated resources to the result 82 for i := range output { 83 if output[i].GetAnnotations()[ResourceIDAnnotation] == "" { 84 result = append(result, output[i]) 85 } 86 } 87 88 return result 89 } 90 91 // nodeWithResourceID returns the node with the input resourceId 92 func nodeWithResourceID(resourceID string, input []*yaml.RNode) *yaml.RNode { 93 for _, node := range input { 94 if node.GetAnnotations()[ResourceIDAnnotation] == resourceID { 95 return node 96 } 97 } 98 return nil 99 } 100 101 // presentIn returns true if the targetNode identified by kpt-resource-id annotation 102 // is present in the input list of resources 103 func presentIn(targetNode *yaml.RNode, input []*yaml.RNode) bool { 104 id := targetNode.GetAnnotations()[ResourceIDAnnotation] 105 for _, node := range input { 106 if node.GetAnnotations()[ResourceIDAnnotation] == id { 107 return true 108 } 109 } 110 return false 111 } 112 113 // SetResourceIds adds kpt-resource-id annotation to each input resource 114 func SetResourceIds(input []*yaml.RNode) error { 115 id := 0 116 for i := range input { 117 idStr := fmt.Sprintf("%v", id) 118 err := input[i].PipeE(yaml.SetAnnotation(ResourceIDAnnotation, idStr)) 119 if err != nil { 120 return err 121 } 122 id++ 123 } 124 return nil 125 } 126 127 // DeleteResourceIds removes the kpt-resource-id annotation from all resources 128 func DeleteResourceIds(input []*yaml.RNode) error { 129 for i := range input { 130 err := input[i].PipeE(yaml.ClearAnnotation(ResourceIDAnnotation)) 131 if err != nil { 132 return err 133 } 134 } 135 return nil 136 } 137 138 type SelectionContext struct { 139 RootPackagePath types.UniquePath 140 } 141 142 // SelectInput returns the selected resources based on criteria in selectors 143 func SelectInput(input []*yaml.RNode, selectors, exclusions []kptfilev1.Selector, _ *SelectionContext) ([]*yaml.RNode, error) { 144 var selectedInput []*yaml.RNode 145 if len(selectors) == 0 { 146 selectedInput = input 147 } else { 148 for _, node := range input { 149 for _, selector := range selectors { 150 if isMatch(node, selector) { 151 selectedInput = append(selectedInput, node) 152 } 153 } 154 } 155 } 156 if len(exclusions) == 0 { 157 return selectedInput, nil 158 } 159 var filteredInput []*yaml.RNode 160 for _, node := range selectedInput { 161 matchesExclusion := false 162 for _, exclusion := range exclusions { 163 if !exclusion.IsEmpty() && isMatch(node, exclusion) { 164 matchesExclusion = true 165 break 166 } 167 } 168 if !matchesExclusion { 169 filteredInput = append(filteredInput, node) 170 } 171 } 172 return filteredInput, nil 173 } 174 175 // isMatch returns true if the resource matches input selection criteria 176 func isMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { 177 // keep expanding with new selectors 178 return nameMatch(node, selector) && namespaceMatch(node, selector) && 179 kindMatch(node, selector) && apiVersionMatch(node, selector) && 180 labelMatch(node, selector) && annoMatch(node, selector) 181 } 182 183 // nameMatch returns true if the resource name matches input selection criteria 184 func nameMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { 185 return selector.Name == "" || selector.Name == node.GetName() 186 } 187 188 // namespaceMatch returns true if the resource namespace matches input selection criteria 189 func namespaceMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { 190 return selector.Namespace == "" || selector.Namespace == node.GetNamespace() 191 } 192 193 // kindMatch returns true if the resource kind matches input selection criteria 194 func kindMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { 195 return selector.Kind == "" || selector.Kind == node.GetKind() 196 } 197 198 // apiVersionMatch returns true if the resource apiVersion matches input selection criteria 199 func apiVersionMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { 200 return selector.APIVersion == "" || selector.APIVersion == node.GetApiVersion() 201 } 202 203 // labelMatch returns true if the resource labels match input selection criteria 204 func labelMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { 205 nodeLabels := node.GetLabels() 206 for sk, sv := range selector.Labels { 207 if nv, found := nodeLabels[sk]; !found || sv != nv { 208 return false 209 } 210 } 211 return true 212 } 213 214 // annoMatch returns true if the resource annotations match input selection criteria 215 func annoMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { 216 nodeAnnos := node.GetAnnotations() 217 for sk, sv := range selector.Annotations { 218 if nv, found := nodeAnnos[sk]; !found || sv != nv { 219 return false 220 } 221 } 222 return true 223 } 224 225 func NewConfigMap(data map[string]string) (*yaml.RNode, error) { 226 node := yaml.NewMapRNode(&data) 227 if node == nil { 228 return nil, nil 229 } 230 // create a ConfigMap only for configMap config 231 configMap := yaml.MustParse(` 232 apiVersion: v1 233 kind: ConfigMap 234 metadata: 235 name: function-input 236 data: {} 237 `) 238 if err := node.VisitFields(func(node *yaml.MapNode) error { 239 v := node.Value.YNode() 240 v.Tag = yaml.NodeTagString 241 node.Value.SetYNode(v) 242 return nil 243 }); err != nil { 244 return nil, err 245 } 246 if err := configMap.PipeE(yaml.SetField("data", node)); err != nil { 247 return nil, err 248 } 249 return configMap, nil 250 }