knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/configmap/hash-gen/main.go (about) 1 /* 2 Copyright 2020 The Knative 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 main 18 19 import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "log" 24 "os" 25 26 "go.yaml.in/yaml/v3" 27 "knative.dev/pkg/configmap" 28 ) 29 30 func main() { 31 for _, fileName := range os.Args[1:] { 32 if err := processFile(fileName); err != nil { 33 log.Fatal(err) 34 } 35 } 36 } 37 38 // processFile reads the ConfigMap manifest from a file and adds or updates the label 39 // containing the checksum of it's _example data if present. 40 func processFile(fileName string) error { 41 in, err := os.ReadFile(fileName) 42 if err != nil { 43 return fmt.Errorf("failed to read file: %w", err) 44 } 45 46 out, err := process(in) 47 if out == nil || err != nil { 48 return err 49 } 50 51 //nolint:gosec // This is not security critical so open permissions are fine. 52 if err := os.WriteFile(fileName, out, 0o644); err != nil { 53 return fmt.Errorf("failed to write file: %w", err) 54 } 55 return nil 56 } 57 58 // process processes a YAML file's bytes and adds or updates the label containing 59 // the checksum of it's _example data if present. 60 func process(data []byte) ([]byte, error) { 61 var doc yaml.Node 62 if err := yaml.Unmarshal(data, &doc); err != nil { 63 return nil, fmt.Errorf("failed to parse YAML: %w", err) 64 } 65 content := doc.Content[0] 66 67 example := traverse(content, "data", configmap.ExampleKey) 68 if example == nil { 69 return nil, nil 70 } 71 72 metadata := traverse(content, "metadata") 73 if metadata == nil { 74 return nil, errors.New("'metadata' not found") 75 } 76 77 annotations := traverse(metadata, "annotations") 78 if annotations == nil { 79 annotations = &yaml.Node{Kind: yaml.MappingNode} 80 metadata.Content = append(metadata.Content, strNode("annotations"), annotations) 81 } 82 83 checksum := configmap.Checksum(example.Value) 84 existingAnnotation := value(annotations, configmap.ExampleChecksumAnnotation) 85 if existingAnnotation != nil { 86 existingAnnotation.Value = checksum 87 } else { 88 sumNode := strNode(checksum) 89 sumNode.Style = yaml.DoubleQuotedStyle 90 annotations.Content = append(annotations.Content, 91 strNode(configmap.ExampleChecksumAnnotation), sumNode) 92 } 93 94 var buffer bytes.Buffer 95 buffer.Grow(len(data)) 96 encoder := yaml.NewEncoder(&buffer) 97 encoder.SetIndent(2) 98 if err := encoder.Encode(&doc); err != nil { 99 return nil, fmt.Errorf("failed to encode YAML: %w", err) 100 } 101 return buffer.Bytes(), nil 102 } 103 104 // traverse traverses the YAML nodes' children using the given path keys. Returns nil if 105 // one of the keys can't be found. 106 func traverse(parent *yaml.Node, path ...string) *yaml.Node { 107 if parent == nil || len(path) == 0 { 108 return parent 109 } 110 tail := path[1:] 111 child := value(parent, path[0]) 112 return traverse(child, tail...) 113 } 114 115 // value returns the value of the current node under the given key. Returns nil if not 116 // found. 117 func value(parent *yaml.Node, key string) *yaml.Node { 118 for i := range parent.Content { 119 if parent.Content[i].Value == key { 120 // The Content array of a yaml.Node is just an array of more nodes. The keys 121 // to each field are therefore a string node right before the relevant field's 122 // value, hence we need to return the next value of the array to retrieve 123 // the value under a key. 124 if len(parent.Content) < i+2 { 125 return nil 126 } 127 return parent.Content[i+1] 128 } 129 } 130 return nil 131 } 132 133 // strNode generate a node that forces the representation to be a string. 134 func strNode(value string) *yaml.Node { 135 return &yaml.Node{ 136 Kind: yaml.ScalarNode, 137 Tag: "!!str", 138 Value: value, 139 } 140 }