github.com/docker/libcompose@v0.4.1-0.20210616120443-2a046c0bdbf2/config/interpolation.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 8 "github.com/sirupsen/logrus" 9 ) 10 11 var defaultValues = make(map[string]string) 12 13 func isNum(c uint8) bool { 14 return c >= '0' && c <= '9' 15 } 16 17 func validVariableDefault(c uint8, line string, pos int) bool { 18 return (c == ':' && line[pos+1] == '-') || (c == '-') 19 } 20 21 func validVariableNameChar(c uint8) bool { 22 return c == '_' || 23 c >= 'A' && c <= 'Z' || 24 c >= 'a' && c <= 'z' || 25 isNum(c) 26 } 27 28 func parseVariable(line string, pos int, mapping func(string) string) (string, int, bool) { 29 var buffer bytes.Buffer 30 31 for ; pos < len(line); pos++ { 32 c := line[pos] 33 34 switch { 35 case validVariableNameChar(c): 36 buffer.WriteByte(c) 37 default: 38 return mapping(buffer.String()), pos - 1, true 39 } 40 } 41 42 return mapping(buffer.String()), pos, true 43 } 44 45 func parseDefaultValue(line string, pos int) (string, int, bool) { 46 var buffer bytes.Buffer 47 48 // only skip :, :- and - at the beginning 49 for ; pos < len(line); pos++ { 50 c := line[pos] 51 if c == ':' || c == '-' { 52 continue 53 } 54 break 55 } 56 for ; pos < len(line); pos++ { 57 c := line[pos] 58 if c == '}' { 59 return buffer.String(), pos - 1, true 60 } 61 err := buffer.WriteByte(c) 62 if err != nil { 63 return "", pos, false 64 } 65 } 66 return "", 0, false 67 } 68 69 func parseVariableWithBraces(line string, pos int, mapping func(string) string) (string, int, bool) { 70 var buffer bytes.Buffer 71 72 for ; pos < len(line); pos++ { 73 c := line[pos] 74 75 switch { 76 case c == '}': 77 bufferString := buffer.String() 78 79 if bufferString == "" { 80 return "", 0, false 81 } 82 return mapping(buffer.String()), pos, true 83 case validVariableNameChar(c): 84 buffer.WriteByte(c) 85 case validVariableDefault(c, line, pos): 86 defaultValue := "" 87 defaultValue, pos, _ = parseDefaultValue(line, pos) 88 defaultValues[buffer.String()] = defaultValue 89 default: 90 return "", 0, false 91 } 92 } 93 94 return "", 0, false 95 } 96 97 func parseInterpolationExpression(line string, pos int, mapping func(string) string) (string, int, bool) { 98 c := line[pos] 99 100 switch { 101 case c == '$': 102 return "$", pos, true 103 case c == '{': 104 return parseVariableWithBraces(line, pos+1, mapping) 105 case !isNum(c) && validVariableNameChar(c): 106 // Variables can't start with a number 107 return parseVariable(line, pos, mapping) 108 default: 109 return "", 0, false 110 } 111 } 112 113 func parseLine(line string, mapping func(string) string) (string, bool) { 114 var buffer bytes.Buffer 115 116 for pos := 0; pos < len(line); pos++ { 117 c := line[pos] 118 switch { 119 case c == '$': 120 var replaced string 121 var success bool 122 123 replaced, pos, success = parseInterpolationExpression(line, pos+1, mapping) 124 125 if !success { 126 return "", false 127 } 128 129 buffer.WriteString(replaced) 130 default: 131 buffer.WriteByte(c) 132 } 133 } 134 135 return buffer.String(), true 136 } 137 138 func parseConfig(key string, data *interface{}, mapping func(string) string) error { 139 switch typedData := (*data).(type) { 140 case string: 141 var success bool 142 143 *data, success = parseLine(typedData, mapping) 144 145 if !success { 146 return fmt.Errorf("Invalid interpolation format for key \"%s\": \"%s\"", key, typedData) 147 } 148 case []interface{}: 149 for k, v := range typedData { 150 err := parseConfig(key, &v, mapping) 151 152 if err != nil { 153 return err 154 } 155 156 typedData[k] = v 157 } 158 case map[interface{}]interface{}: 159 for k, v := range typedData { 160 err := parseConfig(key, &v, mapping) 161 162 if err != nil { 163 return err 164 } 165 166 typedData[k] = v 167 } 168 } 169 170 return nil 171 } 172 173 // Interpolate replaces variables in a map entry 174 func Interpolate(key string, data *interface{}, environmentLookup EnvironmentLookup) error { 175 return parseConfig(key, data, func(s string) string { 176 values := environmentLookup.Lookup(s, nil) 177 178 if len(values) == 0 { 179 if val, ok := defaultValues[s]; ok { 180 return val 181 } 182 logrus.Warnf("The %s variable is not set. Substituting a blank string.", s) 183 return "" 184 } 185 186 if strings.SplitN(values[0], "=", 2)[1] == "" { 187 if val, ok := defaultValues[s]; ok { 188 return val 189 } 190 } 191 192 // Use first result if many are given 193 value := values[0] 194 195 // Environment variables come in key=value format 196 // Return everything past first '=' 197 return strings.SplitN(value, "=", 2)[1] 198 }) 199 }