- package config
-
- import (
- "bytes"
- "errors"
- "encoding/json"
- "fmt"
- "io/ioutil"
-
- "github.com/ghodss/yaml"
- )
-
- // DefaultConfig contains YAML that is applied before the user's
- // configuration.
- //
- const DefaultConfig = `{
- "lives": {
- "in": "/srv/app",
- "as": "somebody",
- "uid": 65533,
- "gid": 65533
- },
- "runs": {
- "as": "runuser",
- "uid": 900,
- "gid": 900}}`
-
- // ResolveIncludes iterates over and recurses through a given variant's
- // includes to build a flat slice of variant names in the correct order by
- // which they should be expanded/merged. It checks for both the existence of
- // included variants and maintains a recursion stack to protect against
- // infinite loops.
- //
- // Variant names found at a greater depth of recursion are first and siblings
- // last, the order in which config should be merged.
- //
- func ResolveIncludes(config *Config, name string) ([]string, error) {
- stack := map[string]bool{}
- includes := []string{}
-
- var resolve func(string) error
-
- resolve = func(name string) error {
- if instack, found := stack[name]; found && instack {
- return errors.New("variant expansion detected loop")
- }
-
- stack[name] = true
- defer func() { stack[name] = false }()
-
- variant, found := config.Variants[name]
-
- if !found {
- return fmt.Errorf("variant '%s' does not exist", name)
- }
-
- for _, include := range variant.Includes {
- if err := resolve(include); err != nil {
- return err
- }
- }
-
- // Appending _after_ recursion ensures the correct ordering
- includes = append(includes, name)
-
- return nil
- }
-
- err := resolve(name)
-
- return includes, err
- }
-
- // ExpandVariant merges a named variant with a config. It also attempts to
- // recursively expand any included variants in the expanded variant.
- //
- func ExpandVariant(config *Config, name string) (*VariantConfig, error) {
- expanded := new(VariantConfig)
- expanded.CommonConfig.Merge(config.CommonConfig)
-
- includes, err := ResolveIncludes(config, name)
-
- if err != nil {
- return nil, err
- }
-
- for _, include := range includes {
- expanded.Merge(config.Variants[include])
- }
-
- return expanded, nil
- }
- // ReadYAMLConfig converts YAML bytes to json and returns new Config struct.
- //
- func ReadYAMLConfig(data []byte) (*Config, error) {
- jsonData, err := yaml.YAMLToJSON(data)
- if err != nil {
- return nil, err
- }
-
- return ReadConfig(jsonData)
- }
-
- // ReadConfig unmarshals the given YAML bytes into a new Config struct.
- //
- func ReadConfig(data []byte) (*Config, error) {
- var (
- version VersionConfig
- config Config
- )
-
- // Unmarshal config version first for pre-validation
- err := json.Unmarshal(data, &version)
-
- if err != nil {
- return nil, err
- }
-
- if err = Validate(version); err != nil {
- return nil, err
- }
-
- // Unmarshal the default config
- json.Unmarshal([]byte(DefaultConfig), &config)
-
- // And finally strictly decode the entire user-provided config
- dec := json.NewDecoder(bytes.NewReader(data))
- dec.DisallowUnknownFields()
- err = dec.Decode(&config)
-
- if err != nil {
- return nil, err
- }
-
- err = Validate(config)
-
- return &config, err
- }
-
- // ReadConfigFile unmarshals the given YAML file contents into a Config
- // struct.
- //
- func ReadConfigFile(path string) (*Config, error) {
- data, err := ioutil.ReadFile(path)
-
- if err != nil {
- return nil, err
- }
-
- return ReadYAMLConfig(data)
- }
|