github.com/zntrio/harp/v2@v2.0.9/pkg/template/values/hocon/hocon.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package hocon 19 20 import ( 21 "bytes" 22 "encoding/json" 23 "fmt" 24 "os" 25 "path/filepath" 26 27 "github.com/go-akka/configuration" 28 "github.com/go-akka/configuration/hocon" 29 "go.uber.org/zap" 30 31 "github.com/zntrio/harp/v2/pkg/sdk/log" 32 ) 33 34 // Parser is a HOCON parser. 35 type Parser struct{} 36 37 // Unmarshal unmarshals HOCON files. 38 func (i *Parser) Unmarshal(p []byte, v interface{}) error { 39 // Parse HOCON configuration 40 rootCfg := configuration.ParseString(string(p), hoconIncludeCallback).Root() 41 42 // Visit config tree 43 res := visitNode(rootCfg) 44 45 // Encode as json 46 var buf bytes.Buffer 47 if err := json.NewEncoder(&buf).Encode(res); err != nil { 48 return fmt.Errorf("unable to encode HOCON map to JSON: %w", err) 49 } 50 51 // Decode JSON 52 if err := json.Unmarshal(buf.Bytes(), v); err != nil { 53 return fmt.Errorf("unable to decode json object as struct: %w", err) 54 } 55 56 return nil 57 } 58 59 // ----------------------------------------------------------------------------- 60 61 func visitNode(node *hocon.HoconValue) interface{} { 62 if node.IsArray() { 63 nodes := node.GetArray() 64 65 res := make([]interface{}, len(nodes)) 66 for i, n := range nodes { 67 res[i] = visitNode(n) 68 } 69 70 return res 71 } 72 73 if node.IsObject() { 74 obj := node.GetObject() 75 76 res := map[string]interface{}{} 77 keys := obj.GetKeys() 78 for _, k := range keys { 79 res[k] = visitNode(obj.GetKey(k)) 80 } 81 82 return res 83 } 84 85 if node.IsString() { 86 return node.GetString() 87 } 88 89 if node.IsEmpty() { 90 return nil 91 } 92 93 return nil 94 } 95 96 func hoconIncludeCallback(filename string) *hocon.HoconRoot { 97 files, err := filepath.Glob(filename) 98 switch { 99 case err != nil: 100 log.Bg().Error("hocon: unable to load file glob", zap.Error(err), zap.String("filename", filename)) 101 return nil 102 case len(files) == 0: 103 log.Bg().Warn("hocon: unable to load file %s", zap.String("filename", filename)) 104 return hocon.Parse("", nil) 105 default: 106 root := hocon.Parse("", nil) 107 for _, f := range files { 108 data, err := os.ReadFile(filepath.Clean(f)) 109 if err != nil { 110 log.Bg().Error("hocon: unable to load file glob", zap.Error(err)) 111 return nil 112 } 113 114 node := hocon.Parse(string(data), hoconIncludeCallback) 115 if node != nil { 116 root.Value().GetObject().Merge(node.Value().GetObject()) 117 // merge substitutions 118 subs := make([]*hocon.HoconSubstitution, 0) 119 subs = append(subs, root.Substitutions()...) 120 subs = append(subs, node.Substitutions()...) 121 root = hocon.NewHoconRoot(root.Value(), subs...) 122 } 123 } 124 return root 125 } 126 }