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 }