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 }