More complex inheritance in YAML?

喜欢而已 提交于 2021-01-20 14:35:10

问题


YAML has inheritance. The most clear example I have ever seen is here: http://blog.101ideas.cz/posts/dry-your-yaml-files.html

I need something more complex: I need to override object's object's property. Here is an example:

database: &default
  server:
    ip: 192.168.1.5
    port: 2000
  db_name: test
  user: 
    name: root
    password: root

# database foo differs from default by only its port and user password
foo_database:
  <<: *default
  server:
    port: 2001
  db_name: foo
  user:
    password: foo_root

I want to get this result:

foo_database.server.ip -> 192.168.1.5
foo_database.server.port -> 2001
foo_database.db_name -> foo
foo_database.user.name -> root
foo_database.user.password -> foo_root

But if you declare like this, you will get these properties incorrect (according to expected values):

foo_database.server.ip -> will be None
foo_database.user.name -> will be None

because new "server" object has only "port" property and it overrides whole old "server" object.

How do I get the kind of inheritance which I want to achieve?

Edit

Here is my exact intention with a working code in LiveScript:

config = 
  default: 
    ip: \192.168.1.5
    port: 2000
    name: \root 
    password: \root 
    db:
      name: \default
      location: \LA

  foo-database:~ -> @default `merge` do 
    ip: \11.11.11.11
    db:
      name: \my-foo 

  bar-database:~ -> @foo-database `merge` do 
    password: \1234 
    db:
      location: \SF

config.default 
# => {"ip":"192.168.1.5","port":2000,"name":"root","password":"root","db":{"name":"default","location":"LA"}}
config.foo-database  
# => {"ip":"11.11.11.11","port":2000,"name":"root","password":"root","db":{"name":"my-foo","location":"LA"}}
config.bar-database  
# => {"ip":"11.11.11.11","port":2000,"name":"root","password":"1234","db":{"name":"my-foo","location":"SF"}}

回答1:


Unfortunately, you can't get the kind of "inheritance" you want to achieve because YAML's "inheritance" is more like a form of "merging hashes".

Expanding out your configuration at the point you use the *default alias, you have:

foo_database:
  server:
    ip: 192.168.1.5
    port: 2000
  db_name: test
  user: 
    name: root
    password: root

If you use hashes with the same keys afterwards, they will completely overwrite the hashes declared earlier, leaving you with (excuse the formatting):

foo_database:

  server:
    ip: 192.168.1.5
    port: 2000
  db_name: test
  user: 
   name: root
   password: root  

  server:
    port: 2001
  db_name: foo
  user:
    password: foo_root

So, in your case, it would seem that since the config is not exactly the same, DRYing up your configuration using anchors and aliases probably isn't the right approach.

More references on this issue below:

  • Rake, YAML and Inherited Build Configuration
  • Merging hashes in yaml conf files

Edit

If you really wanted to, I think you could reconfigure your YAML as below to get exactly what you want, but in your case, I would say the extra obfuscation isn't worth it:

server_defaults: &server_defaults
  ip: 192.168.1.5
  port: 2000

user_defaults: &user_defaults
  name: root
  password: root

database: &default
  server:
    <<: *server_defaults
  db_name: test
  user: 
    <<: *user_defaults

foo_database:
  <<: *default
  server:
    <<: *server_defaults
    port: 2001
  db_name: foo
  user:
    <<: *user_defaults
    password: foo_root



回答2:


How about this? Use multiple anchors.

database: &default
  server: &server
    ip: 192.168.1.5
    port: 2000
  db_name: test
  user: &user
    name: root
    password: root

foo_database:
  <<: *default
  server:
    << : *server
    port: 2001
  db_name: foo
  user:
    << : *user
    password: foo_root

It's just a little extra work, and slightly harder to read than if what you wanted were built into YAML as you suggested (I thought it would work that way too). But overall not bad.




回答3:


For this sort of problems, I have created a tool: jq-front. By using yq + jq-front, you can achieve it by slightly modifying your input.

in.yaml:

$local: 
  database_default:
    server:
      ip: 192.168.1.5
      port: 2000
    db_name: test
    user: 
      name: root
      password: root

# database foo differs from default by only its port and user password
foo_database:
  $extends: [ database_default ]
  server:
    port: 2001
  db_name: foo
  user:
    password: foo_root

And you can process this file by a following command line.

$ yq . -j in.yaml | jq-front  | yq . -y

And you will get following output that you wanted.

foo_database:
  server:
    ip: 192.168.1.5
    port: 2001
  db_name: foo
  user:
    name: root
    password: foo_root

NOTE: jq-front is very slow. On my machine the command took 2.5s, which did not matter to me too much since system configuration can be read once and only the converted file is used by the rest of my program.

NOTE: If you use docker + bash, it's lot easier to install jq-front by docker. You only need to add following function to your .bashrc or a file that is sourced by it.

function jq-front() {
  docker run --rm -i \
    -v /:/var/lib/jf \
    -e JF_PATH_BASE="/var/lib/jf" \
    -e JF_PATH="${JF_PATH}" \
    -e JF_DEBUG=${JF_DEBUG:-disabled} \
    -e JF_CWD="$(pwd)" \
    dakusui/jq-front:"${JF_DOCKER_TAG:-latest}" "${@}"
}


来源:https://stackoverflow.com/questions/14184971/more-complex-inheritance-in-yaml

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!