pkg/template/values/hocon/hocon.go (80 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package hocon
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/go-akka/configuration"
"github.com/go-akka/configuration/hocon"
"go.uber.org/zap"
"github.com/elastic/harp/pkg/sdk/log"
)
// Parser is a HOCON parser
type Parser struct{}
// Unmarshal unmarshals HOCON files
func (i *Parser) Unmarshal(p []byte, v interface{}) error {
// Parse HOCON configuration
rootCfg := configuration.ParseString(string(p), hoconIncludeCallback).Root()
// Visit config tree
res := visitNode(rootCfg)
// Encode as json
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(res); err != nil {
return fmt.Errorf("unable to encode HOCON map to JSON: %w", err)
}
// Decode JSON
if err := json.Unmarshal(buf.Bytes(), v); err != nil {
return fmt.Errorf("unable to decode json object as struct: %w", err)
}
return nil
}
// -----------------------------------------------------------------------------
func visitNode(node *hocon.HoconValue) interface{} {
if node.IsArray() {
nodes := node.GetArray()
res := make([]interface{}, len(nodes))
for i, n := range nodes {
res[i] = visitNode(n)
}
return res
}
if node.IsObject() {
obj := node.GetObject()
res := map[string]interface{}{}
keys := obj.GetKeys()
for _, k := range keys {
res[k] = visitNode(obj.GetKey(k))
}
return res
}
if node.IsString() {
return node.GetString()
}
if node.IsEmpty() {
return nil
}
return nil
}
func hoconIncludeCallback(filename string) *hocon.HoconRoot {
files, err := filepath.Glob(filename)
switch {
case err != nil:
log.Bg().Error("hocon: unable to load file glob", zap.Error(err), zap.String("filename", filename))
return nil
case len(files) == 0:
log.Bg().Warn("hocon: unable to load file %s", zap.String("filename", filename))
return hocon.Parse("", nil)
default:
root := hocon.Parse("", nil)
for _, f := range files {
data, err := os.ReadFile(f)
if err != nil {
log.Bg().Error("hocon: unable to load file glob", zap.Error(err))
return nil
}
node := hocon.Parse(string(data), hoconIncludeCallback)
if node != nil {
root.Value().GetObject().Merge(node.Value().GetObject())
// merge substitutions
subs := make([]*hocon.HoconSubstitution, 0)
subs = append(subs, root.Substitutions()...)
subs = append(subs, node.Substitutions()...)
root = hocon.NewHoconRoot(root.Value(), subs...)
}
}
return root
}
}