github.com/yanndegat/hiera@v0.6.8/config/config.go (about)

     1  // Package config contains the code to load and resolve the Hiera configuration
     2  package config
     3  
     4  import (
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/lyraproj/dgo/dgo"
    12  	"github.com/lyraproj/dgo/tf"
    13  	"github.com/lyraproj/dgo/util"
    14  	"github.com/lyraproj/dgoyaml/yaml"
    15  	"github.com/yanndegat/hiera/api"
    16  )
    17  
    18  type (
    19  	hieraCfg struct {
    20  		root             string
    21  		path             string
    22  		defaults         *entry
    23  		hierarchy        []api.Entry
    24  		defaultHierarchy []api.Entry
    25  	}
    26  )
    27  
    28  const definitions = `{
    29  	options=map[/\A[A-Za-z](:?[0-9A-Za-z_-]*[0-9A-Za-z])?\z/]data,
    30  	rstring=string[1],
    31  	defaults={
    32  	  options?:options,
    33  	  data_dig?:rstring,
    34  	  data_hash?:rstring,
    35  	  lookup_key?:rstring,
    36  	  datadir?:rstring,
    37  	  plugindir?:rstring
    38  	},
    39  	entry={
    40  	  name:rstring,
    41  	  options?:options,
    42  	  data_dig?:rstring,
    43  	  data_hash?:rstring,
    44  	  lookup_key?:rstring,
    45  	  datadir?:rstring,
    46  	  plugindir?:rstring,
    47  	  pluginfile?:rstring,
    48  	  path?:rstring,
    49  	  paths?:[1]rstring,
    50  	  glob?:rstring,
    51  	  globs?:[1]rstring,
    52  	  uri?:rstring,
    53  	  uris?:[1]rstring,
    54  	  mapped_paths?:[3,3]rstring
    55  	}
    56  }`
    57  
    58  const hieraTypeString = `{
    59  	version:5,
    60  	defaults?:defaults,
    61  	hierarchy?:[]entry,
    62  	default_hierarchy?:[]entry
    63  }`
    64  
    65  // FileName is the default file name for the Hiera configuration file.
    66  const FileName = `hiera.yaml`
    67  
    68  var cfgType dgo.Type
    69  
    70  func init() {
    71  	tf.ParseType(definitions)
    72  	cfgType = tf.ParseType(hieraTypeString)
    73  }
    74  
    75  // New creates a new unresolved Config from the given path. If the path does not exist, the
    76  // default config is returned.
    77  func New(configPath string) api.Config {
    78  	content, err := ioutil.ReadFile(configPath)
    79  	if err != nil {
    80  		if !os.IsNotExist(err) {
    81  			panic(err)
    82  		}
    83  		dc := &hieraCfg{
    84  			root:             filepath.Dir(configPath),
    85  			path:             ``,
    86  			defaultHierarchy: []api.Entry{},
    87  		}
    88  		dc.defaults = dc.makeDefaultConfig()
    89  		dc.hierarchy = dc.makeDefaultHierarchy()
    90  		return dc
    91  	}
    92  
    93  	yv, err := yaml.Unmarshal(content)
    94  	if err != nil {
    95  		panic(err)
    96  	}
    97  	cfgMap := yv.(dgo.Map)
    98  	if !cfgType.Instance(cfgMap) {
    99  		panic(tf.IllegalAssignment(cfgType, cfgMap))
   100  	}
   101  
   102  	return createConfig(configPath, cfgMap)
   103  }
   104  
   105  func createConfig(path string, hash dgo.Map) api.Config {
   106  	cfg := &hieraCfg{root: filepath.Dir(path), path: path}
   107  
   108  	if dv := hash.Get(`defaults`); dv != nil {
   109  		cfg.defaults = cfg.createEntry(`defaults`, dv.(dgo.Map)).(*entry)
   110  	} else {
   111  		cfg.defaults = cfg.makeDefaultConfig()
   112  	}
   113  
   114  	if hv := hash.Get(`hierarchy`); hv != nil {
   115  		cfg.hierarchy = cfg.createHierarchy(hv.(dgo.Array))
   116  	} else {
   117  		cfg.hierarchy = cfg.makeDefaultHierarchy()
   118  	}
   119  
   120  	if hv := hash.Get(`default_hierarchy`); hv != nil {
   121  		cfg.defaultHierarchy = cfg.createHierarchy(hv.(dgo.Array))
   122  	}
   123  
   124  	return cfg
   125  }
   126  
   127  func defaultDataDir() string {
   128  	dataDir, exists := os.LookupEnv("HIERA_DATADIR")
   129  	if !exists {
   130  		dataDir = `data`
   131  	}
   132  	return dataDir
   133  }
   134  
   135  func defaultPluginDir() string {
   136  	pluginDir, exists := os.LookupEnv("HIERA_PLUGINDIR")
   137  	if !exists {
   138  		pluginDir = `plugin`
   139  	}
   140  	return pluginDir
   141  }
   142  
   143  func (hc *hieraCfg) makeDefaultConfig() *entry {
   144  	return &entry{
   145  		cfg:       hc,
   146  		dataDir:   defaultDataDir(),
   147  		pluginDir: defaultPluginDir(),
   148  		function:  &function{kind: api.KindDataHash, name: `yaml_data`},
   149  	}
   150  }
   151  
   152  func (hc *hieraCfg) makeDefaultHierarchy() []api.Entry {
   153  	return []api.Entry{
   154  		// The lyra default behavior is to look for a <Hiera root>/data.yaml. Hiera root is the current directory.
   155  		&entry{cfg: hc, dataDir: `.`, name: `Root`, locations: []api.Location{NewPath(`data.yaml`)}},
   156  		// Hiera proper default behavior is to look for <Hiera root>/data/common.yaml
   157  		&entry{cfg: hc, name: `Common`, locations: []api.Location{NewPath(`common.yaml`)}}}
   158  }
   159  
   160  func (hc *hieraCfg) Hierarchy() []api.Entry {
   161  	return hc.hierarchy
   162  }
   163  
   164  func (hc *hieraCfg) DefaultHierarchy() []api.Entry {
   165  	return hc.defaultHierarchy
   166  }
   167  
   168  func (hc *hieraCfg) Root() string {
   169  	return hc.root
   170  }
   171  
   172  func (hc *hieraCfg) Path() string {
   173  	return hc.path
   174  }
   175  
   176  func (hc *hieraCfg) Defaults() api.Entry {
   177  	return hc.defaults
   178  }
   179  
   180  func (hc *hieraCfg) createHierarchy(hierarchy dgo.Array) []api.Entry {
   181  	entries := make([]api.Entry, 0, hierarchy.Len())
   182  	uniqueNames := make(map[string]bool, hierarchy.Len())
   183  	hierarchy.Each(func(hv dgo.Value) {
   184  		hh := hv.(dgo.Map)
   185  		name := ``
   186  		if nv := hh.Get(`name`); nv != nil {
   187  			name = nv.String()
   188  		}
   189  		if uniqueNames[name] {
   190  			panic(fmt.Errorf(`hierarchy name '%s' defined more than once`, name))
   191  		}
   192  		uniqueNames[name] = true
   193  		entries = append(entries, hc.createEntry(name, hh))
   194  	})
   195  	return entries
   196  }
   197  
   198  func (hc *hieraCfg) createEntry(name string, entryHash dgo.Map) api.Entry {
   199  	entry := &entry{cfg: hc, name: name}
   200  	entry.initialize(name, entryHash)
   201  	entryHash.EachEntry(func(me dgo.MapEntry) {
   202  		ks := me.Key().String()
   203  		v := me.Value()
   204  		switch {
   205  		case ks == `datadir`:
   206  			entry.dataDir = v.String()
   207  		case ks == `plugindir`:
   208  			entry.pluginDir = v.String()
   209  		case ks == `pluginfile`:
   210  			entry.pluginFile = v.String()
   211  		case util.ContainsString(LocationKeys, ks):
   212  			if entry.locations != nil {
   213  				panic(fmt.Errorf(`only one of %s can be defined in hierarchy '%s'`, strings.Join(LocationKeys, `, `), name))
   214  			}
   215  			switch ks {
   216  			case `path`:
   217  				entry.locations = []api.Location{NewPath(v.String())}
   218  			case `paths`:
   219  				a := v.(dgo.Array)
   220  				entry.locations = make([]api.Location, 0, a.Len())
   221  				a.Each(func(p dgo.Value) { entry.locations = append(entry.locations, NewPath(p.String())) })
   222  			case `glob`:
   223  				entry.locations = []api.Location{NewGlob(v.String())}
   224  			case `globs`:
   225  				a := v.(dgo.Array)
   226  				entry.locations = make([]api.Location, 0, a.Len())
   227  				a.Each(func(p dgo.Value) { entry.locations = append(entry.locations, NewGlob(p.String())) })
   228  			case `uri`:
   229  				entry.locations = []api.Location{NewURI(v.String())}
   230  			case `uris`:
   231  				a := v.(dgo.Array)
   232  				entry.locations = make([]api.Location, 0, a.Len())
   233  				a.Each(func(p dgo.Value) { entry.locations = append(entry.locations, NewURI(p.String())) })
   234  			default: // Mapped paths
   235  				a := v.(dgo.Array)
   236  				entry.locations = []api.Location{NewMappedPaths(a.Get(0).String(), a.Get(1).String(), a.Get(2).String())}
   237  			}
   238  		}
   239  	})
   240  	return entry
   241  }