You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

151 lines
3.2 KiB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
  1. package config
  2. import (
  3. "bytes"
  4. "errors"
  5. "encoding/json"
  6. "fmt"
  7. "io/ioutil"
  8. "github.com/ghodss/yaml"
  9. )
  10. // DefaultConfig contains YAML that is applied before the user's
  11. // configuration.
  12. //
  13. const DefaultConfig = `{
  14. "lives": {
  15. "in": "/srv/app",
  16. "as": "somebody",
  17. "uid": 65533,
  18. "gid": 65533
  19. },
  20. "runs": {
  21. "as": "runuser",
  22. "uid": 900,
  23. "gid": 900}}`
  24. // ResolveIncludes iterates over and recurses through a given variant's
  25. // includes to build a flat slice of variant names in the correct order by
  26. // which they should be expanded/merged. It checks for both the existence of
  27. // included variants and maintains a recursion stack to protect against
  28. // infinite loops.
  29. //
  30. // Variant names found at a greater depth of recursion are first and siblings
  31. // last, the order in which config should be merged.
  32. //
  33. func ResolveIncludes(config *Config, name string) ([]string, error) {
  34. stack := map[string]bool{}
  35. includes := []string{}
  36. var resolve func(string) error
  37. resolve = func(name string) error {
  38. if instack, found := stack[name]; found && instack {
  39. return errors.New("variant expansion detected loop")
  40. }
  41. stack[name] = true
  42. defer func() { stack[name] = false }()
  43. variant, found := config.Variants[name]
  44. if !found {
  45. return fmt.Errorf("variant '%s' does not exist", name)
  46. }
  47. for _, include := range variant.Includes {
  48. if err := resolve(include); err != nil {
  49. return err
  50. }
  51. }
  52. // Appending _after_ recursion ensures the correct ordering
  53. includes = append(includes, name)
  54. return nil
  55. }
  56. err := resolve(name)
  57. return includes, err
  58. }
  59. // ExpandVariant merges a named variant with a config. It also attempts to
  60. // recursively expand any included variants in the expanded variant.
  61. //
  62. func ExpandVariant(config *Config, name string) (*VariantConfig, error) {
  63. expanded := new(VariantConfig)
  64. expanded.CommonConfig.Merge(config.CommonConfig)
  65. includes, err := ResolveIncludes(config, name)
  66. if err != nil {
  67. return nil, err
  68. }
  69. for _, include := range includes {
  70. expanded.Merge(config.Variants[include])
  71. }
  72. return expanded, nil
  73. }
  74. // ReadYAMLConfig converts YAML bytes to json and returns new Config struct.
  75. //
  76. func ReadYAMLConfig(data []byte) (*Config, error) {
  77. jsonData, err := yaml.YAMLToJSON(data)
  78. if err != nil {
  79. return nil, err
  80. }
  81. return ReadConfig(jsonData)
  82. }
  83. // ReadConfig unmarshals the given YAML bytes into a new Config struct.
  84. //
  85. func ReadConfig(data []byte) (*Config, error) {
  86. var (
  87. version VersionConfig
  88. config Config
  89. )
  90. // Unmarshal config version first for pre-validation
  91. err := json.Unmarshal(data, &version)
  92. if err != nil {
  93. return nil, err
  94. }
  95. if err = Validate(version); err != nil {
  96. return nil, err
  97. }
  98. // Unmarshal the default config
  99. json.Unmarshal([]byte(DefaultConfig), &config)
  100. // And finally strictly decode the entire user-provided config
  101. dec := json.NewDecoder(bytes.NewReader(data))
  102. dec.DisallowUnknownFields()
  103. err = dec.Decode(&config)
  104. if err != nil {
  105. return nil, err
  106. }
  107. err = Validate(config)
  108. return &config, err
  109. }
  110. // ReadConfigFile unmarshals the given YAML file contents into a Config
  111. // struct.
  112. //
  113. func ReadConfigFile(path string) (*Config, error) {
  114. data, err := ioutil.ReadFile(path)
  115. if err != nil {
  116. return nil, err
  117. }
  118. return ReadYAMLConfig(data)
  119. }