templating/values.go (89 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package templating import ( "fmt" "log" "os" "github.com/Azure/acr-builder/util" yaml "gopkg.in/yaml.v2" ) // Values represents a map of build values. type Values map[string]interface{} // ToYAMLString encodes the Values object into a YAML string. func (v Values) ToYAMLString() (string, error) { b, err := yaml.Marshal(v) return string(b), err } // Deserialize will convert the specified bytes to a Values object. func Deserialize(b []byte) (v Values, err error) { v = Values{} err = yaml.Unmarshal(b, &v) if len(v) == 0 { v = Values{} } return v, err } // DeserializeFromFile will parse the specified file name and convert it // to a Values object. func DeserializeFromFile(fileName string) (Values, error) { b, err := os.ReadFile(fileName) if err != nil { return map[string]interface{}{}, err } return Deserialize(b) } // OverrideValues overrides the first config with the second. func OverrideValues(c1 *Config, c2 *Config) (Values, error) { merged := Values{} if c2 != nil { v, err := Deserialize([]byte(c2.GetRawValue())) if err != nil { return merged, err } merged, err = merge(c1, v) if err != nil { return merged, err } } return merged, nil } // merge merges the specified template with the specified map. // The specified map has precedence. func merge(c *Config, merged map[string]interface{}) (map[string]interface{}, error) { if !c.IsValidConfig() { return merged, nil } vals, err := Deserialize([]byte(c.GetRawValue())) if err != nil { return merged, fmt.Errorf("failed to deserialize values. Try rendering your template locally using the instructions found at https://github.com/Azure/acr-builder. Err: %v", err) } for k, v := range vals { if lookup, ok := merged[k]; ok { // If the lookup is nil, remove the key. // This allows us to remove keys during overrides if they no longer exist. // I.e., someone broke compatibility in a future template. if lookup == nil { delete(merged, k) } else if sink, ok := lookup.(map[string]interface{}); ok { source, ok := v.(map[string]interface{}) if !ok { log.Printf("Skip merging: %s. Not a map\n", k) continue } // The to-be-merged value has precedence over the start value. mergeMaps(sink, source) } } else { // If the key doesn't exist, copy it. merged[k] = v } } return merged, nil } // mergeMaps merges two maps. func mergeMaps(sink, source map[string]interface{}) map[string]interface{} { for k, v := range source { // Try to pull out a map from the source if _, ok := v.(map[string]interface{}); ok { // 1. If the key doesn't exist, set it. // 2. If the key exists and it's a map, recursively iterate through the map. // 3. Otherwise, the key is trying to be overridden by a scalar value, in which // case print a warning message and skip it. if innerV, ok := sink[k]; !ok { sink[k] = v } else if util.IsInterfaceMap(innerV) { mergeMaps(innerV.(map[string]interface{}), v.(map[string]interface{})) } else { log.Printf("Skip merging: %s. Can't override a map with a scalar %v\n", k, v) } } else { sv, ok := sink[k] if ok && util.IsInterfaceMap(sv) { log.Printf("Skip merging: %s is a map but %v is not\n", k, v) } else { sink[k] = v } } } return sink }