- package config
-
- import (
- "errors"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "reflect"
- "strings"
-
- "github.com/utahta/go-openuri"
- "github.com/ghodss/yaml"
- )
-
- // Policy validates a number of rules against a given configuration.
- //
- type Policy struct {
- Enforcements []Enforcement `json:"enforcements"`
- }
-
- // Validate checks the given config against all policy enforcements.
- //
- func (pol Policy) Validate(config Config) error {
- validate := newValidator()
-
- for _, enforcement := range pol.Enforcements {
- cfg, err := ResolveJSONPath(enforcement.Path, config)
-
- if err != nil {
- // If the path resolved nothing, there's nothing to enforce
- continue
- }
-
- // Flags are a special case in which the True field should be compared
- // against the validator, not the struct itself.
- if flag, ok := cfg.(Flag); ok {
- cfg = flag.True
- }
-
- err = validate.Var(cfg, enforcement.Rule)
-
- if err != nil {
- return fmt.Errorf(
- `value for "%s" violates policy rule "%s"`,
- enforcement.Path, enforcement.Rule,
- )
- }
- }
-
- return nil
- }
-
- // Enforcement represents a policy rule and config path on which to apply it.
- //
- type Enforcement struct {
- Path string `json:"path"`
- Rule string `json:"rule"`
- }
-
- // ReadYAMLPolicy converts YAML input to JSON and returns a new Policy struct.
- //
- func ReadYAMLPolicy(data []byte) (*Policy, error) {
- jsonData, err := yaml.YAMLToJSON(data)
- if err != nil {
- return nil, err
- }
-
- return ReadPolicy(jsonData)
- }
-
- // ReadPolicy unmarshals the given YAML/json bytes into a new Policy struct.
- //
- func ReadPolicy(data []byte) (*Policy, error) {
- var policy Policy
- err := json.Unmarshal(data, &policy)
-
- if err != nil {
- return nil, err
- }
-
- return &policy, err
- }
-
- // ReadPolicyFromURI fetches the policy file from the given URL or file path
- // and loads its contents with ReadPolicy.
- //
- func ReadPolicyFromURI(uri string) (*Policy, error) {
- io, err := openuri.Open(uri)
-
- if err != nil {
- return nil, err
- }
-
- defer io.Close()
-
- data, err := ioutil.ReadAll(io)
-
- if err != nil {
- return nil, err
- }
-
- return ReadYAMLPolicy(data)
- }
-
- // ResolveJSONPath returns the config value found at the given JSON-ish
- // namespace/path (e.g. "variants.production.runs.as").
- //
- func ResolveJSONPath(path string, cfg interface{}) (interface{}, error) {
- parts := strings.SplitN(path, ".", 2)
- name := parts[0]
-
- v := reflect.ValueOf(cfg)
- t := v.Type()
-
- var subcfg interface{}
-
- switch t.Kind() {
- case reflect.Struct:
- for i := 0; i < t.NumField(); i++ {
- if t.Field(i).Anonymous {
- if subsubcfg, err := ResolveJSONPath(path, v.Field(i).Interface()); err == nil {
- return subsubcfg, nil
- }
- }
-
- if name == resolveJSONTagName(t.Field(i)) {
- subcfg = v.Field(i).Interface()
- break
- }
- }
-
- case reflect.Map:
- if t.Key().Kind() == reflect.String {
- for _, key := range v.MapKeys() {
- if key.Interface().(string) == name {
- subcfg = v.MapIndex(key).Interface()
- break
- }
- }
- }
- }
-
- if subcfg == nil {
- return nil, errors.New("invalid path")
- }
-
- if len(parts) > 1 {
- return ResolveJSONPath(parts[1], subcfg)
- }
-
- return subcfg, nil
- }
|