github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/client/taskenv/util.go (about)

     1  package taskenv
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/zclconf/go-cty/cty"
     9  )
    10  
    11  var (
    12  	// ErrInvalidObjectPath is returned when a key cannot be converted into
    13  	// a nested object path like "foo...bar", ".foo", or "foo."
    14  	ErrInvalidObjectPath = errors.New("invalid object path")
    15  )
    16  
    17  // addNestedKey expands keys into their nested form:
    18  //
    19  //	k="foo.bar", v="quux" -> {"foo": {"bar": "quux"}}
    20  //
    21  // Existing keys are overwritten. Map values take precedence over primitives.
    22  //
    23  // If the key has dots but cannot be converted to a valid nested data structure
    24  // (eg "foo...bar", "foo.", or non-object value exists for key), an error is
    25  // returned.
    26  func addNestedKey(dst map[string]interface{}, k, v string) error {
    27  	// createdParent and Key capture the parent object of the first created
    28  	// object and the first created object's key respectively. The cleanup
    29  	// func deletes them to prevent side-effects when returning errors.
    30  	var createdParent map[string]interface{}
    31  	var createdKey string
    32  	cleanup := func() {
    33  		if createdParent != nil {
    34  			delete(createdParent, createdKey)
    35  		}
    36  	}
    37  
    38  	segments := strings.Split(k, ".")
    39  	for _, newKey := range segments[:len(segments)-1] {
    40  		if newKey == "" {
    41  			// String either begins with a dot (.foo) or has at
    42  			// least two consecutive dots (foo..bar); either way
    43  			// it's an invalid object path.
    44  			cleanup()
    45  			return ErrInvalidObjectPath
    46  		}
    47  
    48  		var target map[string]interface{}
    49  		if existingI, ok := dst[newKey]; ok {
    50  			if existing, ok := existingI.(map[string]interface{}); ok {
    51  				// Target already exists
    52  				target = existing
    53  			} else {
    54  				// Existing value is not a map. Maps should
    55  				// take precedence over primitive values (eg
    56  				// overwrite attr.driver.qemu = "1" with
    57  				// attr.driver.qemu.version = "...")
    58  				target = make(map[string]interface{})
    59  				dst[newKey] = target
    60  			}
    61  		} else {
    62  			// Does not exist, create
    63  			target = make(map[string]interface{})
    64  			dst[newKey] = target
    65  
    66  			// If this is the first created key, capture it for
    67  			// cleanup if there is an error later.
    68  			if createdParent == nil {
    69  				createdParent = dst
    70  				createdKey = newKey
    71  			}
    72  		}
    73  
    74  		// Descend into new m
    75  		dst = target
    76  	}
    77  
    78  	// See if the final segment is a valid key
    79  	newKey := segments[len(segments)-1]
    80  	if newKey == "" {
    81  		// String ends in a dot
    82  		cleanup()
    83  		return ErrInvalidObjectPath
    84  	}
    85  
    86  	if existingI, ok := dst[newKey]; ok {
    87  		if _, ok := existingI.(map[string]interface{}); ok {
    88  			// Existing value is a map which takes precedence over
    89  			// a primitive value. Drop primitive.
    90  			return nil
    91  		}
    92  	}
    93  	dst[newKey] = v
    94  	return nil
    95  }
    96  
    97  // ctyify converts nested map[string]interfaces to a map[string]cty.Value. An
    98  // error is returned if an unsupported type is encountered.
    99  //
   100  // Currently only strings, cty.Values, and nested maps are supported.
   101  func ctyify(src map[string]interface{}) (map[string]cty.Value, error) {
   102  	dst := make(map[string]cty.Value, len(src))
   103  
   104  	for k, vI := range src {
   105  		switch v := vI.(type) {
   106  		case string:
   107  			dst[k] = cty.StringVal(v)
   108  
   109  		case cty.Value:
   110  			dst[k] = v
   111  
   112  		case map[string]interface{}:
   113  			o, err := ctyify(v)
   114  			if err != nil {
   115  				return nil, err
   116  			}
   117  			dst[k] = cty.ObjectVal(o)
   118  
   119  		default:
   120  			return nil, fmt.Errorf("key %q has invalid type %T", k, v)
   121  		}
   122  	}
   123  
   124  	return dst, nil
   125  }