github.com/lyraproj/hiera@v1.0.0-rc4/config/config.go (about)

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