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  }