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 }