load.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. // Copyright 2021-2023 EMQ Technologies Co., Ltd.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // INTECH Process Automation Ltd.
  15. //
  16. // Licensed under the Apache License, Version 2.0 (the "License");
  17. // you may not use this file except in compliance with the License.
  18. // You may obtain a copy of the License at
  19. //
  20. // http://www.apache.org/licenses/LICENSE-2.0
  21. //
  22. // Unless required by applicable law or agreed to in writing, software
  23. // distributed under the License is distributed on an "AS IS" BASIS,
  24. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25. // See the License for the specific language governing permissions and
  26. // limitations under the License.
  27. package conf
  28. import (
  29. "encoding/json"
  30. "errors"
  31. "fmt"
  32. "os"
  33. "path"
  34. "path/filepath"
  35. "strconv"
  36. "strings"
  37. "github.com/mitchellh/mapstructure"
  38. "gopkg.in/yaml.v3"
  39. )
  40. const Separator = "__"
  41. func LoadConfig(c interface{}) error {
  42. return LoadConfigByName(ConfFileName, c)
  43. }
  44. func LoadConfigByName(name string, c interface{}) error {
  45. dir, err := GetConfLoc()
  46. if err != nil {
  47. return err
  48. }
  49. p := path.Join(dir, name)
  50. return LoadConfigFromPath(p, c)
  51. }
  52. func LoadConfigFromPath(p string, c interface{}) error {
  53. prefix := getPrefix(p)
  54. b, err := os.ReadFile(p)
  55. if err != nil {
  56. return err
  57. }
  58. configMap := make(map[string]interface{})
  59. err = yaml.Unmarshal(b, &configMap)
  60. if err != nil {
  61. return err
  62. }
  63. // Make all keys to lowercase to match environment variables then revert it back by checking json defs
  64. configs := normalize(configMap)
  65. err = process(configs, os.Environ(), prefix)
  66. if err != nil {
  67. return err
  68. }
  69. // checking json keys
  70. switch c.(type) {
  71. case *map[string]interface{}, *map[string]map[string]interface{}:
  72. names, err := extractKeysFromJsonIfExists(p)
  73. if err != nil {
  74. return err
  75. }
  76. applyKeys(configs, names)
  77. }
  78. return mapstructure.Decode(configs, c)
  79. }
  80. func CorrectsConfigKeysByJson(configs map[string]interface{}, jsonFilePath string) error {
  81. dir, err := GetConfLoc()
  82. if err != nil {
  83. return err
  84. }
  85. path := path.Join(dir, jsonFilePath)
  86. m, err := loadJsonForYaml(path)
  87. if err != nil {
  88. return err
  89. }
  90. names, err := extractNamesFromProperties(m)
  91. if err != nil {
  92. return err
  93. }
  94. applyKeys(configs, names)
  95. return nil
  96. }
  97. func getPrefix(p string) string {
  98. _, file := path.Split(p)
  99. return strings.ToUpper(strings.TrimSuffix(file, filepath.Ext(file)))
  100. }
  101. func process(configMap map[string]interface{}, variables []string, prefix string) error {
  102. for _, e := range variables {
  103. if !strings.HasPrefix(e, prefix) {
  104. continue
  105. }
  106. pair := strings.SplitN(e, "=", 2)
  107. if len(pair) != 2 {
  108. return fmt.Errorf("wrong format of variable")
  109. }
  110. keys := nameToKeys(trimPrefix(pair[0], prefix))
  111. handle(configMap, keys, pair[1])
  112. printableK := strings.Join(keys, ".")
  113. printableV := pair[1]
  114. if strings.Contains(strings.ToLower(printableK), "password") {
  115. printableV = "*"
  116. }
  117. Log.Infof("Set config '%s.%s' to '%s' by environment variable", strings.ToLower(prefix), printableK, printableV)
  118. }
  119. return nil
  120. }
  121. func handle(conf map[string]interface{}, keysLeft []string, val string) {
  122. key := getConfigKey(keysLeft[0])
  123. if len(keysLeft) == 1 {
  124. conf[key] = getValueType(val)
  125. } else if len(keysLeft) >= 2 {
  126. if v, ok := conf[key]; ok {
  127. if casted, castSuccess := v.(map[string]interface{}); castSuccess {
  128. handle(casted, keysLeft[1:], val)
  129. } else {
  130. panic("not expected type")
  131. }
  132. } else {
  133. next := make(map[string]interface{})
  134. conf[key] = next
  135. handle(next, keysLeft[1:], val)
  136. }
  137. }
  138. }
  139. func trimPrefix(key string, prefix string) string {
  140. p := fmt.Sprintf("%s%s", prefix, Separator)
  141. return strings.TrimPrefix(key, p)
  142. }
  143. func nameToKeys(key string) []string {
  144. return strings.Split(strings.ToLower(key), Separator)
  145. }
  146. func getConfigKey(key string) string {
  147. return strings.ToLower(key)
  148. }
  149. func getValueType(val string) interface{} {
  150. val = strings.Trim(val, " ")
  151. if strings.HasPrefix(val, "[") && strings.HasSuffix(val, "]") {
  152. val = strings.ReplaceAll(val, "[", "")
  153. val = strings.ReplaceAll(val, "]", "")
  154. vals := strings.Split(val, ",")
  155. var ret []interface{}
  156. for _, v := range vals {
  157. if i, err := strconv.ParseInt(v, 10, 64); err == nil {
  158. ret = append(ret, i)
  159. } else if b, err := strconv.ParseBool(v); err == nil {
  160. ret = append(ret, b)
  161. } else if f, err := strconv.ParseFloat(v, 64); err == nil {
  162. ret = append(ret, f)
  163. } else {
  164. ret = append(ret, v)
  165. }
  166. }
  167. return ret
  168. } else if i, err := strconv.ParseInt(val, 10, 64); err == nil {
  169. return i
  170. } else if b, err := strconv.ParseBool(val); err == nil {
  171. return b
  172. } else if f, err := strconv.ParseFloat(val, 64); err == nil {
  173. return f
  174. }
  175. return val
  176. }
  177. func normalize(m map[string]interface{}) map[string]interface{} {
  178. res := make(map[string]interface{})
  179. for k, v := range m {
  180. lowered := strings.ToLower(k)
  181. if casted, success := v.(map[string]interface{}); success {
  182. node := normalize(casted)
  183. res[lowered] = node
  184. } else {
  185. res[lowered] = v
  186. }
  187. }
  188. return res
  189. }
  190. func applyKeys(m map[string]interface{}, list []string) {
  191. for _, k := range list {
  192. applyKey(m, k)
  193. }
  194. }
  195. func applyKey(m map[string]interface{}, key string) {
  196. for k, v := range m {
  197. if casted, ok := v.(map[string]interface{}); ok {
  198. applyKey(casted, key)
  199. }
  200. if key != k && strings.EqualFold(key, k) {
  201. m[key] = v
  202. delete(m, k)
  203. }
  204. }
  205. }
  206. func extractKeysFromJsonIfExists(yamlPath string) ([]string, error) {
  207. jsonFilePath := jsonPathForFile(yamlPath)
  208. _, err := os.Stat(jsonFilePath)
  209. if err != nil {
  210. if errors.Is(err, os.ErrNotExist) {
  211. return make([]string, 0), nil
  212. } else {
  213. return nil, err
  214. }
  215. }
  216. m, err := loadJsonForYaml(jsonFilePath)
  217. if err != nil {
  218. return nil, err
  219. }
  220. return extractNamesFromProperties(m)
  221. }
  222. func loadJsonForYaml(filePath string) (map[string]interface{}, error) {
  223. data, err := os.ReadFile(filePath)
  224. if err != nil {
  225. return nil, err
  226. }
  227. m := make(map[string]interface{})
  228. err = json.Unmarshal(data, &m)
  229. if err != nil {
  230. return nil, err
  231. }
  232. return m, nil
  233. }
  234. func jsonPathForFile(yamlPath string) string {
  235. p := strings.TrimSuffix(yamlPath, filepath.Ext(yamlPath))
  236. return fmt.Sprintf("%s.json", p)
  237. }
  238. func extractNamesFromProperties(jsonMap map[string]interface{}) ([]string, error) {
  239. result := make([]string, 0)
  240. properties, contains := jsonMap["properties"]
  241. if !contains {
  242. return nil, fmt.Errorf("json map does not have properties value")
  243. }
  244. if propertiesAsMap, success := properties.(map[string]interface{}); success {
  245. re := extractNamesFromElement(propertiesAsMap)
  246. result = append(result, re...)
  247. } else {
  248. return nil, fmt.Errorf("failed to cast to list of properties")
  249. }
  250. return result, nil
  251. }
  252. func extractNamesFromElement(jsonMap map[string]interface{}) []string {
  253. result := make([]string, 0)
  254. list := jsonMap["default"]
  255. switch lt := list.(type) {
  256. case []interface{}:
  257. if len(lt) != 0 {
  258. for _, element := range lt {
  259. if m, isMap := element.(map[string]interface{}); isMap {
  260. re := extractNamesFromElement(m)
  261. result = append(result, re...)
  262. }
  263. }
  264. return result
  265. }
  266. case map[string]interface{}:
  267. if len(lt) != 0 {
  268. for _, element := range lt {
  269. if m, isMap := element.(map[string]interface{}); isMap {
  270. re := extractNamesFromElement(m)
  271. result = append(result, re...)
  272. }
  273. }
  274. return result
  275. }
  276. }
  277. // If not a list/map, or an empty list/map, then it's a single element
  278. n := jsonMap["name"]
  279. if s, isString := n.(string); isString {
  280. result = append(result, s)
  281. }
  282. return result
  283. }
  284. func Printable(m map[string]interface{}) map[string]interface{} {
  285. printableMap := make(map[string]interface{})
  286. for k, v := range m {
  287. if strings.ToLower(k) == "password" {
  288. printableMap[k] = "***"
  289. } else {
  290. if vm, ok := v.(map[string]interface{}); ok {
  291. printableMap[k] = Printable(vm)
  292. } else {
  293. printableMap[k] = v
  294. }
  295. }
  296. }
  297. return printableMap
  298. }