douzhouqin6223 2018-10-09 11:09
浏览 181
已采纳

用作Golang中Yaml数据目标的默认结构

I try to improve the user experience of a cli I maintain. A main goal is to provide reasonable defaults. It uses yaml extensively for configuration.

A basic demo implementation of the configuration can be found here: https://github.com/unprofession-al/configuration/tree/bf5a89b3eee7338899b28c047f3795546ce3d2e6

General

The main configuration looks like this:

type Config map[string]ConfigSection

type ConfigSection struct {
    Input  InputConfig  `yaml:"input"`
    Output OutputConfig `yaml:"output"`
}

Config holds a bunch of ConfigSections. This allow the user to define variations of the configuration (lets say prod, dev and testing for example) and use YAML Achors to do so.

The parts of the ConfigSection (Input and Output) would be defined in the package that consumes the configuration. Each of this part provides a Defaults() and a custom UnmarshalYAML() func. Also ConfigSection itself provides an UnmarshalYAML() func. This idea is stolen from https://github.com/go-yaml/yaml/issues/165#issuecomment-255223956.

Question

In data.go in the repo some test input as well as the expected output is defined. Running the tests (go test -v) shows:

  • Having nothing defined in the ConfigSection (empty example) no defaults are applied.
  • If a part (of a ConfigSection) with no data field is defined, this part will have no defaults. The "undefined" part has defaults (see input, output).
  • If both parts are defined (as is section both) but have no data fields, any defaults are set.

I see no pattern at all and ran out of ideas why this works like this and how to get the expected results (eg. get the test to pass).

  • 写回答

1条回答 默认 最新

  • dongren1353 2018-10-10 07:02
    关注

    Ok, so the pattern I did not see was quite obviouse: The "deepest leaf" of a configuration overwrites everything below, either with the given data or with the go defaults for empty values:

    That means that a struct like this...

    [key_string]:
      input:
        listener: [string]
        static: [string]
      output:
        listener: [string]
        details:
          filter: [string]
          retention: [string]
    

    ... defaulted with the data...

    defaults:
      input:
        listener: 127.0.0.1:8910
        static: default
      output:
        listener: 127.0.0.1:8989
        details:
          filter: '*foo*'
          retention: 3h
    

    ... feeded with a yaml of this form..

    empty:
    
    both:
      input:
      output:
    
    input: &input
      input:
    
    input-modified-with-anchor:
      <<: *input
      input:
        static: NOTDEFAULT
    
    input-modified-without-anchor:
      input:
        static: NOTDEFAULT
    
    output: &output
      output:
    
    output-modified-with-anchor:
      <<: *output
      output:
        details:
          filter: NOTDEFAULT
    
    output-modified-without-anchor:
      output:
        details:
          filter: NOTDEFAULT
    

    ... turn out as...

    both:
      input:
        listener: ""
        static: ""
      output:
        listener: ""
        details:
          filter: ""
          retention: ""
    empty:
      input:
        listener: ""
        static: ""
      output:
        listener: ""
        details:
          filter: ""
          retention: ""
    input:
      input:
        listener: ""
        static: ""
      output:
        listener: 127.0.0.1:8989
        details:
          filter: '*foo*'
          retention: 3h
    input-modified-with-anchor:
      input:
        listener: 127.0.0.1:8910
        static: NOTDEFAULT
      output:
        listener: 127.0.0.1:8989
        details:
          filter: '*foo*'
          retention: 3h
    input-modified-without-anchor:
      input:
        listener: 127.0.0.1:8910
        static: NOTDEFAULT
      output:
        listener: 127.0.0.1:8989
        details:
          filter: '*foo*'
          retention: 3h
    

    For my use case this is a too complicated behavior, I therefore try a different approach: I if demanded I inject a default config into the yaml and put a reference to its anchor in each section. I feel like this is more transparent and reproducible to the end user. Heres an ugly draft of the func:

    func injectYAML(data []byte) ([]byte, error) {
        // render a default section an add an anchor
        key := "injected_defaults"
        defaultData := Config{key: Defaults()}
        var defaultSection []byte
        defaultSection, _ = yaml.Marshal(defaultData)
        defaultSection = bytes.Replace(defaultSection, []byte(key+":"), []byte(key+": &"+key), 1)
    
        // get list of sections in input data
        c := Config{}
        err := yaml.Unmarshal(data, &c)
        if err != nil {
            return data, fmt.Errorf("Error while reading sections from yaml: %s", err.Error())
        }
    
        // remove "---" at beginning when present
        data = bytes.TrimLeft(data, "---")
    
        // add reference to default section to each section
        lines := bytes.Split(data, []byte("
    "))
        var updatedLines [][]byte
        for _, line := range lines {
            updatedLines = append(updatedLines, line)
            for section := range c {
                if bytes.HasPrefix(line, []byte(section+":")) {
                    updatedLines = append(updatedLines, []byte("  <<: *"+key))
                }
            }
        }
        updatedData := bytes.Join(updatedLines, []byte("
    "))
    
        // compose injected yaml
        out := []byte("---
    ")
        out = append(out, defaultSection...)
        out = append(out, updatedData...)
        return out, nil
    }
    

    Full example at: https://github.com/unprofession-al/configuration/tree/7c2eb7da58b51f52b50f2a0fbac193c799c9eb08

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥100 Jenkins自动化部署—悬赏100元
  • ¥15 关于#python#的问题:求帮写python代码
  • ¥20 MATLAB画图图形出现上下震荡的线条
  • ¥15 关于#windows#的问题:怎么用WIN 11系统的电脑 克隆WIN NT3.51-4.0系统的硬盘
  • ¥15 perl MISA分析p3_in脚本出错
  • ¥15 k8s部署jupyterlab,jupyterlab保存不了文件
  • ¥15 ubuntu虚拟机打包apk错误
  • ¥199 rust编程架构设计的方案 有偿
  • ¥15 回答4f系统的像差计算
  • ¥15 java如何提取出pdf里的文字?