├── tests ├── __init__.py ├── files │ ├── encode_erlang_atom.in │ ├── encode_yaml_null.in │ ├── encode_yaml_null.out │ ├── encode_erlang_atom.out │ ├── encode_ini_null.out │ ├── encode_yaml_block.out │ ├── encode_json_string.in │ ├── encode_lua_string.in │ ├── encode_toml_string.in │ ├── encode_yaml_string.in │ ├── encode_yaml_string.out │ ├── encode_yaml_string_quote.out │ ├── encode_lua_string.out │ ├── encode_toml_string.out │ ├── encode_yaml_block.in │ ├── encode_toml_array.out │ ├── encode_toml_string_quote.out │ ├── encode_apache_boolean.out │ ├── encode_apache_number.out │ ├── encode_json_string.out │ ├── encode_erlang_boolean.in │ ├── encode_erlang_number.in │ ├── encode_ini_general.out │ ├── encode_apache_boolean_convert.out │ ├── encode_apache_number_convert.out │ ├── encode_apache_number_quote.out │ ├── encode_xml_element.in │ ├── encode_ini_general.in │ ├── encode_toml_array.in │ ├── encode_erlang_boolean.out │ ├── encode_erlang_number.out │ ├── encode_xml_attribute.out │ ├── encode_erlang_boolean_convert.out │ ├── encode_erlang_number_convert.out │ ├── encode_ini_null.in │ ├── encode_ini_section.out │ ├── encode_xml_attribute.in │ ├── encode_xml_element.out │ ├── encode_erlang_string.in │ ├── encode_ini_mixed.out │ ├── encode_ini_section.in │ ├── encode_apache_string.out │ ├── encode_ini_mixed_comment.out │ ├── encode_ini_mixed_ucase.out │ ├── encode_apache_boolean.in │ ├── encode_apache_string_quote.out │ ├── encode_ini_mixed.in │ ├── encode_ini_mixed_indent.out │ ├── encode_json_number.in │ ├── encode_lua_number.in │ ├── encode_toml_number.in │ ├── encode_yaml_number.in │ ├── encode_apache_number.in │ ├── encode_ini_mixed_quote.out │ ├── encode_yaml_number.out │ ├── encode_yaml_number_convert.out │ ├── encode_ini_mixed_delimiter.out │ ├── encode_toml_number.out │ ├── encode_toml_number_convert.out │ ├── encode_erlang_string.out │ ├── encode_lua_number.out │ ├── encode_lua_number_convert.out │ ├── encode_lua_list.in │ ├── encode_json_list.in │ ├── encode_toml_table.out │ ├── encode_yaml_list.in │ ├── encode_yaml_list.out │ ├── encode_json_dict.in │ ├── encode_lua_dict.in │ ├── encode_yaml_dict.in │ ├── encode_apache_string.in │ ├── encode_json_number.out │ ├── encode_json_number_convert.out │ ├── encode_json_boolean.in │ ├── encode_lua_boolean.in │ ├── encode_toml_boolean.in │ ├── encode_toml_table.in │ ├── encode_yaml_boolean.in │ ├── encode_yaml_boolean.out │ ├── encode_yaml_boolean_convert.out │ ├── encode_yaml_dict.out │ ├── encode_toml_boolean.out │ ├── encode_toml_boolean_convert.out │ ├── encode_lua_boolean.out │ ├── encode_lua_boolean_convert.out │ ├── encode_yaml_list_indent.out │ ├── encode_apache_vhost.out │ ├── encode_json_boolean.out │ ├── encode_json_boolean_convert.out │ ├── encode_json_dict.out │ ├── encode_lua_list_indent.out │ ├── encode_lua_dict.out │ ├── encode_json_list.out │ ├── encode_toml_table_array.in │ ├── encode_lua_list.out │ ├── encode_apache_vhost.in │ ├── encode_erlang_mixed.in │ ├── encode_toml_table_array.out │ ├── encode_json_list_indent.out │ ├── encode_erlang_mixed.out │ ├── encode_toml_table_grafana.in │ └── encode_toml_table_grafana.out ├── test_config_encoders.include ├── test_config_encoders.yaml └── test_config_encoders.py ├── filter_plugins ├── __init__.py └── config_encoders.py ├── .gitignore ├── test-requirements.txt ├── .travis.yml ├── tox.ini ├── meta └── main.yaml ├── LICENSE └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /filter_plugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tox 2 | *.pyc 3 | *.retry 4 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | flake8==3.9.2 2 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_atom.in: -------------------------------------------------------------------------------- 1 | - aaa: :bbb 2 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_null.in: -------------------------------------------------------------------------------- 1 | var1: 2 | var2: null 3 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_null.out: -------------------------------------------------------------------------------- 1 | var1: null 2 | var2: null 3 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_atom.out: -------------------------------------------------------------------------------- 1 | [ 2 | {aaa, bbb} 3 | ]. 4 | -------------------------------------------------------------------------------- /tests/files/encode_ini_null.out: -------------------------------------------------------------------------------- 1 | var3 2 | 3 | [section1] 4 | var3 5 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_block.out: -------------------------------------------------------------------------------- 1 | zzz: |- 2 | aaa = bbb 3 | ccc = ddd 4 | -------------------------------------------------------------------------------- /tests/files/encode_json_string.in: -------------------------------------------------------------------------------- 1 | var1: my string 2 | var2: "my' \"string\"" 3 | -------------------------------------------------------------------------------- /tests/files/encode_lua_string.in: -------------------------------------------------------------------------------- 1 | var1: my string 2 | var2: "my' \"string\"" 3 | -------------------------------------------------------------------------------- /tests/files/encode_toml_string.in: -------------------------------------------------------------------------------- 1 | var1: my string 2 | var2: "my' \"string\"" 3 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_string.in: -------------------------------------------------------------------------------- 1 | var1: my string 2 | var2: "my' \"string\"" 3 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_string.out: -------------------------------------------------------------------------------- 1 | var1: "my string" 2 | var2: "my' \"string\"" 3 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_string_quote.out: -------------------------------------------------------------------------------- 1 | var1: my string 2 | var2: my' "string" 3 | -------------------------------------------------------------------------------- /tests/files/encode_lua_string.out: -------------------------------------------------------------------------------- 1 | var1 = "my string"; 2 | var2 = "my' \"string\""; 3 | -------------------------------------------------------------------------------- /tests/files/encode_toml_string.out: -------------------------------------------------------------------------------- 1 | var1 = "my string" 2 | var2 = "my' \"string\"" 3 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_block.in: -------------------------------------------------------------------------------- 1 | zzz: |- 2 | ;;;|- 3 | aaa = bbb 4 | ccc = ddd 5 | -------------------------------------------------------------------------------- /tests/files/encode_toml_array.out: -------------------------------------------------------------------------------- 1 | arr1 = ["some text", [123, 987.654], true, "false"] 2 | -------------------------------------------------------------------------------- /tests/files/encode_toml_string_quote.out: -------------------------------------------------------------------------------- 1 | var1 = 'my string' 2 | var2 = 'my\' "string"' 3 | -------------------------------------------------------------------------------- /tests/files/encode_apache_boolean.out: -------------------------------------------------------------------------------- 1 | option1 true 2 | option2 false 3 | option3 true 4 | option4 False 5 | -------------------------------------------------------------------------------- /tests/files/encode_apache_number.out: -------------------------------------------------------------------------------- 1 | option1 123 2 | option2 987.654 3 | option3 123 4 | option4 987.654 5 | -------------------------------------------------------------------------------- /tests/files/encode_json_string.out: -------------------------------------------------------------------------------- 1 | { 2 | "var1": "my string", 3 | "var2": "my' \"string\"" 4 | } 5 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_boolean.in: -------------------------------------------------------------------------------- 1 | - var1: yes 2 | - var2: false 3 | - var3: "True" 4 | - var4: "false" 5 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_number.in: -------------------------------------------------------------------------------- 1 | - var1: 123 2 | - var2: 987.654 3 | - var3: "123" 4 | - var4: "987.654" 5 | -------------------------------------------------------------------------------- /tests/files/encode_ini_general.out: -------------------------------------------------------------------------------- 1 | var1=some text 2 | var2=123 3 | var3=987.654 4 | var4=True 5 | var4=false 6 | -------------------------------------------------------------------------------- /tests/files/encode_apache_boolean_convert.out: -------------------------------------------------------------------------------- 1 | option1 true 2 | option2 false 3 | option3 true 4 | option4 false 5 | -------------------------------------------------------------------------------- /tests/files/encode_apache_number_convert.out: -------------------------------------------------------------------------------- 1 | option1 123 2 | option2 987.654 3 | option3 123 4 | option4 987.654 5 | -------------------------------------------------------------------------------- /tests/files/encode_apache_number_quote.out: -------------------------------------------------------------------------------- 1 | option1 "123" 2 | option2 "987.654" 3 | option3 123 4 | option4 987.654 5 | -------------------------------------------------------------------------------- /tests/files/encode_xml_element.in: -------------------------------------------------------------------------------- 1 | element: 2 | - element1: 3 | - element2: '' 4 | - element3: some text 5 | -------------------------------------------------------------------------------- /tests/files/encode_ini_general.in: -------------------------------------------------------------------------------- 1 | var1: some text 2 | var2: 123 3 | var3: 987.654 4 | var4: 5 | - yes 6 | - "false" 7 | -------------------------------------------------------------------------------- /tests/files/encode_toml_array.in: -------------------------------------------------------------------------------- 1 | arr1: 2 | - some text 3 | - 4 | - 123 5 | - 987.654 6 | - yes 7 | - "false" 8 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_boolean.out: -------------------------------------------------------------------------------- 1 | [ 2 | {var1, true}, 3 | {var2, false}, 4 | {var3, "True"}, 5 | {var4, "false"} 6 | ]. 7 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_number.out: -------------------------------------------------------------------------------- 1 | [ 2 | {var1, 123}, 3 | {var2, 987.654}, 4 | {var3, "123"}, 5 | {var4, "987.654"} 6 | ]. 7 | -------------------------------------------------------------------------------- /tests/files/encode_xml_attribute.out: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_boolean_convert.out: -------------------------------------------------------------------------------- 1 | [ 2 | {var1, true}, 3 | {var2, false}, 4 | {var3, true}, 5 | {var4, false} 6 | ]. 7 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_number_convert.out: -------------------------------------------------------------------------------- 1 | [ 2 | {var1, 123}, 3 | {var2, 987.654}, 4 | {var3, 123}, 5 | {var4, 987.654} 6 | ]. 7 | -------------------------------------------------------------------------------- /tests/files/encode_ini_null.in: -------------------------------------------------------------------------------- 1 | var1: null 2 | var2: 3 | var3: "!!!null" 4 | 5 | section1: 6 | var1: null 7 | var2: 8 | var3: "!!!null" 9 | -------------------------------------------------------------------------------- /tests/files/encode_ini_section.out: -------------------------------------------------------------------------------- 1 | [section1] 2 | var1=some text 3 | var2=123 4 | var3=987.654 5 | 6 | [section2] 7 | var4=True 8 | var5=false 9 | -------------------------------------------------------------------------------- /tests/files/encode_xml_attribute.in: -------------------------------------------------------------------------------- 1 | element: 2 | - ^attr1: val1 3 | - element1: 4 | - ^attr3: val3 5 | - ^attr4: val4 6 | - ^attr2: val2 7 | -------------------------------------------------------------------------------- /tests/files/encode_xml_element.out: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | some text 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_string.in: -------------------------------------------------------------------------------- 1 | - var1: some_text 2 | - var2: some text 3 | - var3: "some 'text'" 4 | - var4: 'some "text"' 5 | - var5: "some \"text\"" 6 | -------------------------------------------------------------------------------- /tests/files/encode_ini_mixed.out: -------------------------------------------------------------------------------- 1 | var1=some text 2 | var2=123 3 | 4 | [section1] 5 | var3=987.654 6 | 7 | [section2] 8 | var4=True 9 | var5=false 10 | -------------------------------------------------------------------------------- /tests/files/encode_ini_section.in: -------------------------------------------------------------------------------- 1 | section1: 2 | var1: some text 3 | var2: 123 4 | var3: 987.654 5 | 6 | section2: 7 | var4: yes 8 | var5: "false" 9 | -------------------------------------------------------------------------------- /tests/files/encode_apache_string.out: -------------------------------------------------------------------------------- 1 | option1 some_text 2 | option2 "some text" 3 | option3 "some 'text'" 4 | option4 "some \"text\"" 5 | option5 "some \"text\"" 6 | -------------------------------------------------------------------------------- /tests/files/encode_ini_mixed_comment.out: -------------------------------------------------------------------------------- 1 | var1=some text 2 | var2=123 3 | 4 | # section1 5 | var3=987.654 6 | 7 | # section2 8 | var4=True 9 | var5=false 10 | -------------------------------------------------------------------------------- /tests/files/encode_ini_mixed_ucase.out: -------------------------------------------------------------------------------- 1 | VAR1=some text 2 | VAR2=123 3 | 4 | [section1] 5 | VAR3=987.654 6 | 7 | [section2] 8 | VAR4=True 9 | VAR5=false 10 | -------------------------------------------------------------------------------- /tests/files/encode_apache_boolean.in: -------------------------------------------------------------------------------- 1 | content: 2 | - options: 3 | - option1: yes 4 | - option2: no 5 | - option3: "true" 6 | - option4: "False" 7 | -------------------------------------------------------------------------------- /tests/files/encode_apache_string_quote.out: -------------------------------------------------------------------------------- 1 | option1 "some_text" 2 | option2 "some text" 3 | option3 "some 'text'" 4 | option4 "some \"text\"" 5 | option5 "some \"text\"" 6 | -------------------------------------------------------------------------------- /tests/files/encode_ini_mixed.in: -------------------------------------------------------------------------------- 1 | var1: some text 2 | var2: 123 3 | 4 | section1: 5 | var3: 987.654 6 | 7 | section2: 8 | var4: yes 9 | var5: "false" 10 | -------------------------------------------------------------------------------- /tests/files/encode_ini_mixed_indent.out: -------------------------------------------------------------------------------- 1 | var1=some text 2 | var2=123 3 | 4 | [section1] 5 | var3=987.654 6 | 7 | [section2] 8 | var4=True 9 | var5=false 10 | -------------------------------------------------------------------------------- /tests/files/encode_json_number.in: -------------------------------------------------------------------------------- 1 | var1: 123 2 | var2: -987 3 | var3: 123.456 4 | var4: -987.654 5 | var5: "123" 6 | var6: "-987" 7 | var7: "123.456" 8 | var8: "-987.654" 9 | -------------------------------------------------------------------------------- /tests/files/encode_lua_number.in: -------------------------------------------------------------------------------- 1 | var1: 123 2 | var2: -987 3 | var3: 123.456 4 | var4: -987.654 5 | var5: "123" 6 | var6: "-987" 7 | var7: "123.456" 8 | var8: "-987.654" 9 | -------------------------------------------------------------------------------- /tests/files/encode_toml_number.in: -------------------------------------------------------------------------------- 1 | var1: 123 2 | var2: -987 3 | var3: 123.456 4 | var4: -987.654 5 | var5: "123" 6 | var6: "-987" 7 | var7: "123.456" 8 | var8: "-987.654" 9 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_number.in: -------------------------------------------------------------------------------- 1 | var1: 123 2 | var2: -987 3 | var3: 123.456 4 | var4: -987.654 5 | var5: "123" 6 | var6: "-987" 7 | var7: "123.456" 8 | var8: "-987.654" 9 | -------------------------------------------------------------------------------- /tests/files/encode_apache_number.in: -------------------------------------------------------------------------------- 1 | content: 2 | - options: 3 | - option1: 123 4 | - option2: 987.654 5 | - option3: "123" 6 | - option4: "987.654" 7 | -------------------------------------------------------------------------------- /tests/files/encode_ini_mixed_quote.out: -------------------------------------------------------------------------------- 1 | var1="some text" 2 | var2="123" 3 | 4 | [section1] 5 | var3="987.654" 6 | 7 | [section2] 8 | var4="True" 9 | var5="false" 10 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_number.out: -------------------------------------------------------------------------------- 1 | var1: 123 2 | var2: -987 3 | var3: 123.456 4 | var4: -987.654 5 | var5: "123" 6 | var6: "-987" 7 | var7: "123.456" 8 | var8: "-987.654" 9 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_number_convert.out: -------------------------------------------------------------------------------- 1 | var1: 123 2 | var2: -987 3 | var3: 123.456 4 | var4: -987.654 5 | var5: 123 6 | var6: -987 7 | var7: 123.456 8 | var8: -987.654 9 | -------------------------------------------------------------------------------- /tests/files/encode_ini_mixed_delimiter.out: -------------------------------------------------------------------------------- 1 | var1 = some text 2 | var2 = 123 3 | 4 | [section1] 5 | var3 = 987.654 6 | 7 | [section2] 8 | var4 = True 9 | var5 = false 10 | -------------------------------------------------------------------------------- /tests/files/encode_toml_number.out: -------------------------------------------------------------------------------- 1 | var1 = 123 2 | var2 = -987 3 | var3 = 123.456 4 | var4 = -987.654 5 | var5 = "123" 6 | var6 = "-987" 7 | var7 = "123.456" 8 | var8 = "-987.654" 9 | -------------------------------------------------------------------------------- /tests/files/encode_toml_number_convert.out: -------------------------------------------------------------------------------- 1 | var1 = 123 2 | var2 = -987 3 | var3 = 123.456 4 | var4 = -987.654 5 | var5 = 123 6 | var6 = -987 7 | var7 = 123.456 8 | var8 = -987.654 9 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_string.out: -------------------------------------------------------------------------------- 1 | [ 2 | {var1, "some_text"}, 3 | {var2, "some text"}, 4 | {var3, "some 'text'"}, 5 | {var4, "some \"text\""}, 6 | {var5, "some \"text\""} 7 | ]. 8 | -------------------------------------------------------------------------------- /tests/files/encode_lua_number.out: -------------------------------------------------------------------------------- 1 | var1 = 123; 2 | var2 = -987; 3 | var3 = 123.456; 4 | var4 = -987.654; 5 | var5 = "123"; 6 | var6 = "-987"; 7 | var7 = "123.456"; 8 | var8 = "-987.654"; 9 | -------------------------------------------------------------------------------- /tests/files/encode_lua_number_convert.out: -------------------------------------------------------------------------------- 1 | var1 = 123; 2 | var2 = -987; 3 | var3 = 123.456; 4 | var4 = -987.654; 5 | var5 = 123; 6 | var6 = -987; 7 | var7 = 123.456; 8 | var8 = -987.654; 9 | -------------------------------------------------------------------------------- /tests/files/encode_lua_list.in: -------------------------------------------------------------------------------- 1 | var1: 2 | - aaa 3 | - 123 4 | 5 | var2: 6 | - aaa 7 | - bbb: 8 | ccc: ddd 9 | eee: 10 | - fff 11 | - ggg: hhh 12 | - bool: yes 13 | -------------------------------------------------------------------------------- /tests/files/encode_json_list.in: -------------------------------------------------------------------------------- 1 | var1: 2 | - aaa 3 | - 123 4 | 5 | var2: 6 | - aaa 7 | - bbb: 8 | ccc: ddd 9 | eee: 10 | - fff 11 | - ggg: hhh 12 | - bool: yes 13 | -------------------------------------------------------------------------------- /tests/files/encode_toml_table.out: -------------------------------------------------------------------------------- 1 | [dog."tater.man"] 2 | type = "pug" 3 | 4 | [table-1] 5 | key1 = "some string" 6 | key2 = 123 7 | 8 | [x.y] 9 | aaa = "bbb" 10 | 11 | [x.y.z.w] 12 | name = "deep table" 13 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_list.in: -------------------------------------------------------------------------------- 1 | var1: 2 | - aaa 3 | - 123 4 | 5 | var2: 6 | - aaa 7 | - bbb: 8 | ccc: ddd 9 | eee: 10 | - fff 11 | - ggg: hhh 12 | - bool: yes 13 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_list.out: -------------------------------------------------------------------------------- 1 | var1: 2 | - "aaa" 3 | - 123 4 | var2: 5 | - "aaa" 6 | - bbb: 7 | ccc: "ddd" 8 | eee: 9 | - "fff" 10 | - ggg: "hhh" 11 | - bool: true 12 | -------------------------------------------------------------------------------- /tests/files/encode_json_dict.in: -------------------------------------------------------------------------------- 1 | var1: 2 | aaa: bbb 3 | ccc: 123 4 | ddd: yes 5 | eee: 6 | fff: ggg 7 | hhh: 8 | iii: jjj 9 | kkk: yes 10 | lll: 11 | - 987.654 12 | - mmm 13 | -------------------------------------------------------------------------------- /tests/files/encode_lua_dict.in: -------------------------------------------------------------------------------- 1 | var1: 2 | aaa: bbb 3 | ccc: 123 4 | ddd: yes 5 | eee: 6 | fff: ggg 7 | hhh: 8 | iii: jjj 9 | kkk: yes 10 | lll: 11 | - 987.654 12 | - mmm 13 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_dict.in: -------------------------------------------------------------------------------- 1 | var1: 2 | aaa: bbb 3 | ccc: 123 4 | ddd: yes 5 | eee: 6 | fff: ggg 7 | hhh: 8 | iii: jjj 9 | kkk: yes 10 | lll: 11 | - 987.654 12 | - mmm 13 | -------------------------------------------------------------------------------- /tests/files/encode_apache_string.in: -------------------------------------------------------------------------------- 1 | content: 2 | - options: 3 | - option1: some_text 4 | - option2: some text 5 | - option3: "some 'text'" 6 | - option4: 'some "text"' 7 | - option5: "some \"text\"" 8 | -------------------------------------------------------------------------------- /tests/files/encode_json_number.out: -------------------------------------------------------------------------------- 1 | { 2 | "var1": 123, 3 | "var2": -987, 4 | "var3": 123.456, 5 | "var4": -987.654, 6 | "var5": "123", 7 | "var6": "-987", 8 | "var7": "123.456", 9 | "var8": "-987.654" 10 | } 11 | -------------------------------------------------------------------------------- /tests/files/encode_json_number_convert.out: -------------------------------------------------------------------------------- 1 | { 2 | "var1": 123, 3 | "var2": -987, 4 | "var3": 123.456, 5 | "var4": -987.654, 6 | "var5": 123, 7 | "var6": -987, 8 | "var7": 123.456, 9 | "var8": -987.654 10 | } 11 | -------------------------------------------------------------------------------- /tests/files/encode_json_boolean.in: -------------------------------------------------------------------------------- 1 | var1: yes 2 | var2: no 3 | var3: true 4 | var4: false 5 | var5: True 6 | var6: False 7 | var7: "yes" 8 | var8: "no" 9 | var9: "true" 10 | var10: "false" 11 | var11: "True" 12 | var12: "False" 13 | -------------------------------------------------------------------------------- /tests/files/encode_lua_boolean.in: -------------------------------------------------------------------------------- 1 | var1: yes 2 | var2: no 3 | var3: true 4 | var4: false 5 | var5: True 6 | var6: False 7 | var7: "yes" 8 | var8: "no" 9 | var9: "true" 10 | var10: "false" 11 | var11: "True" 12 | var12: "False" 13 | -------------------------------------------------------------------------------- /tests/files/encode_toml_boolean.in: -------------------------------------------------------------------------------- 1 | var1: yes 2 | var2: no 3 | var3: true 4 | var4: false 5 | var5: True 6 | var6: False 7 | var7: "yes" 8 | var8: "no" 9 | var9: "true" 10 | var10: "false" 11 | var11: "True" 12 | var12: "False" 13 | -------------------------------------------------------------------------------- /tests/files/encode_toml_table.in: -------------------------------------------------------------------------------- 1 | table-1: 2 | key1: some string 3 | key2: 123 4 | 5 | dog: 6 | tater.man: 7 | type: pug 8 | 9 | x: 10 | y: 11 | aaa: bbb 12 | z: 13 | w: 14 | name: deep table 15 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_boolean.in: -------------------------------------------------------------------------------- 1 | var1: yes 2 | var2: no 3 | var3: true 4 | var4: false 5 | var5: True 6 | var6: False 7 | var7: "yes" 8 | var8: "no" 9 | var9: "true" 10 | var10: "false" 11 | var11: "True" 12 | var12: "False" 13 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_boolean.out: -------------------------------------------------------------------------------- 1 | var1: true 2 | var10: "false" 3 | var11: "True" 4 | var12: "False" 5 | var2: false 6 | var3: true 7 | var4: false 8 | var5: true 9 | var6: false 10 | var7: "yes" 11 | var8: "no" 12 | var9: "true" 13 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_boolean_convert.out: -------------------------------------------------------------------------------- 1 | var1: true 2 | var10: false 3 | var11: true 4 | var12: false 5 | var2: false 6 | var3: true 7 | var4: false 8 | var5: true 9 | var6: false 10 | var7: "yes" 11 | var8: "no" 12 | var9: true 13 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_dict.out: -------------------------------------------------------------------------------- 1 | var1: 2 | aaa: "bbb" 3 | ccc: 123 4 | ddd: true 5 | eee: 6 | fff: "ggg" 7 | hhh: 8 | iii: "jjj" 9 | kkk: true 10 | lll: 11 | - 987.654 12 | - "mmm" 13 | -------------------------------------------------------------------------------- /tests/files/encode_toml_boolean.out: -------------------------------------------------------------------------------- 1 | var1 = true 2 | var10 = "false" 3 | var11 = "True" 4 | var12 = "False" 5 | var2 = false 6 | var3 = true 7 | var4 = false 8 | var5 = true 9 | var6 = false 10 | var7 = "yes" 11 | var8 = "no" 12 | var9 = "true" 13 | -------------------------------------------------------------------------------- /tests/files/encode_toml_boolean_convert.out: -------------------------------------------------------------------------------- 1 | var1 = true 2 | var10 = false 3 | var11 = true 4 | var12 = false 5 | var2 = false 6 | var3 = true 7 | var4 = false 8 | var5 = true 9 | var6 = false 10 | var7 = "yes" 11 | var8 = "no" 12 | var9 = true 13 | -------------------------------------------------------------------------------- /tests/files/encode_lua_boolean.out: -------------------------------------------------------------------------------- 1 | var1 = true; 2 | var10 = "false"; 3 | var11 = "True"; 4 | var12 = "False"; 5 | var2 = false; 6 | var3 = true; 7 | var4 = false; 8 | var5 = true; 9 | var6 = false; 10 | var7 = "yes"; 11 | var8 = "no"; 12 | var9 = "true"; 13 | -------------------------------------------------------------------------------- /tests/files/encode_lua_boolean_convert.out: -------------------------------------------------------------------------------- 1 | var1 = true; 2 | var10 = false; 3 | var11 = true; 4 | var12 = false; 5 | var2 = false; 6 | var3 = true; 7 | var4 = false; 8 | var5 = true; 9 | var6 = false; 10 | var7 = "yes"; 11 | var8 = "no"; 12 | var9 = true; 13 | -------------------------------------------------------------------------------- /tests/files/encode_yaml_list_indent.out: -------------------------------------------------------------------------------- 1 | var1: 2 | - "aaa" 3 | - 123 4 | var2: 5 | - "aaa" 6 | - bbb: 7 | ccc: "ddd" 8 | eee: 9 | - "fff" 10 | - ggg: "hhh" 11 | - bool: true 12 | -------------------------------------------------------------------------------- /tests/files/encode_apache_vhost.out: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot /www/example1 3 | ServerName www.example.com 4 | ErrorLog /var/log/httpd/www.example.com-error_log 5 | CustomLog /var/log/httpd/www.example.com-access_log common 6 | # "Other directives here ..." 7 | 8 | -------------------------------------------------------------------------------- /tests/files/encode_json_boolean.out: -------------------------------------------------------------------------------- 1 | { 2 | "var1": true, 3 | "var10": "false", 4 | "var11": "True", 5 | "var12": "False", 6 | "var2": false, 7 | "var3": true, 8 | "var4": false, 9 | "var5": true, 10 | "var6": false, 11 | "var7": "yes", 12 | "var8": "no", 13 | "var9": "true" 14 | } 15 | -------------------------------------------------------------------------------- /tests/files/encode_json_boolean_convert.out: -------------------------------------------------------------------------------- 1 | { 2 | "var1": true, 3 | "var10": false, 4 | "var11": true, 5 | "var12": false, 6 | "var2": false, 7 | "var3": true, 8 | "var4": false, 9 | "var5": true, 10 | "var6": false, 11 | "var7": "yes", 12 | "var8": "no", 13 | "var9": true 14 | } 15 | -------------------------------------------------------------------------------- /tests/files/encode_json_dict.out: -------------------------------------------------------------------------------- 1 | { 2 | "var1": { 3 | "aaa": "bbb", 4 | "ccc": 123, 5 | "ddd": true, 6 | "eee": { 7 | "fff": "ggg", 8 | "hhh": { 9 | "iii": "jjj", 10 | "kkk": true, 11 | "lll": [ 12 | 987.654, 13 | "mmm" 14 | ] 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/files/encode_lua_list_indent.out: -------------------------------------------------------------------------------- 1 | var1 = { 2 | "aaa"; 3 | 123; 4 | } 5 | var2 = { 6 | "aaa"; 7 | { 8 | bbb = { 9 | ccc = "ddd"; 10 | eee = { 11 | "fff"; 12 | { 13 | ggg = "hhh"; 14 | }; 15 | { 16 | bool = true; 17 | }; 18 | }; 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /tests/files/encode_lua_dict.out: -------------------------------------------------------------------------------- 1 | var1 = { 2 | aaa = "bbb"; 3 | ccc = 123; 4 | ddd = true; 5 | eee = { 6 | fff = "ggg"; 7 | hhh = { 8 | iii = "jjj"; 9 | kkk = true; 10 | lll = { 11 | 987.654; 12 | "mmm"; 13 | }; 14 | }; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /tests/files/encode_json_list.out: -------------------------------------------------------------------------------- 1 | { 2 | "var1": [ 3 | "aaa", 4 | 123 5 | ], 6 | "var2": [ 7 | "aaa", 8 | { 9 | "bbb": { 10 | "ccc": "ddd", 11 | "eee": [ 12 | "fff", 13 | { 14 | "ggg": "hhh" 15 | }, 16 | { 17 | "bool": true 18 | } 19 | ] 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tests/files/encode_toml_table_array.in: -------------------------------------------------------------------------------- 1 | products: 2 | - name: Hammer 3 | sku: 738594937 4 | - {} 5 | - name: Nail 6 | sku: 284758393 7 | color: gray 8 | 9 | fruit: 10 | - name: apple 11 | physical: 12 | color: red 13 | shape: round 14 | variety: 15 | - name: red delicious 16 | - name: granny smith 17 | - name: banana 18 | variety: 19 | - name: plantain 20 | -------------------------------------------------------------------------------- /tests/files/encode_lua_list.out: -------------------------------------------------------------------------------- 1 | var1 = { 2 | "aaa"; 3 | 123; 4 | } 5 | var2 = { 6 | "aaa"; 7 | { 8 | bbb = { 9 | ccc = "ddd"; 10 | eee = { 11 | "fff"; 12 | { 13 | ggg = "hhh"; 14 | }; 15 | { 16 | bool = true; 17 | }; 18 | }; 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /tests/files/encode_apache_vhost.in: -------------------------------------------------------------------------------- 1 | content: 2 | - sections: 3 | - name: VirtualHost 4 | param: "*:80" 5 | content: 6 | - options: 7 | - DocumentRoot: /www/example1 8 | - ServerName: www.example.com 9 | - ErrorLog: /var/log/httpd/www.example.com-error_log 10 | - CustomLog: 11 | - /var/log/httpd/www.example.com-access_log 12 | - common 13 | - "#": Other directives here ... 14 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_mixed.in: -------------------------------------------------------------------------------- 1 | - rabbit: 2 | - tcp_listeners: 3 | - '"127.0.0.1"': 5672 4 | - ssl_listeners: 5 | - 5671 6 | - ssl_options: 7 | - cacertfile: /path/to/testca/cacert.pem 8 | - certfile: /path/to/server/cert.pem 9 | - keyfile: /path/to/server/key.pem 10 | - verify: :verify_peer 11 | - fail_if_no_peer_cert: true 12 | - cluster_nodes: 13 | :: 14 | - 15 | - node1 16 | - node2 17 | - :disc 18 | -------------------------------------------------------------------------------- /tests/files/encode_toml_table_array.out: -------------------------------------------------------------------------------- 1 | [[fruit]] 2 | name = "apple" 3 | 4 | [fruit.physical] 5 | color = "red" 6 | shape = "round" 7 | 8 | [[fruit.variety]] 9 | name = "red delicious" 10 | 11 | [[fruit.variety]] 12 | name = "granny smith" 13 | 14 | [[fruit]] 15 | name = "banana" 16 | 17 | [[fruit.variety]] 18 | name = "plantain" 19 | 20 | [[products]] 21 | name = "Hammer" 22 | sku = 738594937 23 | 24 | [[products]] 25 | 26 | [[products]] 27 | color = "gray" 28 | name = "Nail" 29 | sku = 284758393 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | language: python 4 | dist: focal 5 | 6 | python: 7 | - 2.7 8 | - 3.5 9 | - 3.6 10 | - 3.7 11 | - 3.8 12 | - 3.9 13 | 14 | matrix: 15 | fast_finish: true 16 | 17 | env: 18 | matrix: 19 | - ANSIBLE=2.4 20 | - ANSIBLE=2.5 21 | - ANSIBLE=2.6 22 | - ANSIBLE=2.7 23 | - ANSIBLE=2.8 24 | - ANSIBLE=2.9 25 | - ANSIBLE=2.10 26 | - ANSIBLE=2.11 27 | 28 | install: 29 | - pip install tox-travis 30 | 31 | script: 32 | - tox 33 | 34 | notifications: 35 | email: false 36 | -------------------------------------------------------------------------------- /tests/files/encode_json_list_indent.out: -------------------------------------------------------------------------------- 1 | { 2 | "var1": [ 3 | "aaa", 4 | 123 5 | ], 6 | "var2": [ 7 | "aaa", 8 | { 9 | "bbb": { 10 | "ccc": "ddd", 11 | "eee": [ 12 | "fff", 13 | { 14 | "ggg": "hhh" 15 | }, 16 | { 17 | "bool": true 18 | } 19 | ] 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tests/files/encode_erlang_mixed.out: -------------------------------------------------------------------------------- 1 | [ 2 | {rabbit, [ 3 | {tcp_listeners, [ 4 | {"127.0.0.1", 5672} 5 | ]}, 6 | {ssl_listeners, [ 7 | 5671 8 | ]}, 9 | {ssl_options, [ 10 | {cacertfile, "/path/to/testca/cacert.pem"}, 11 | {certfile, "/path/to/server/cert.pem"}, 12 | {keyfile, "/path/to/server/key.pem"}, 13 | {verify, verify_peer}, 14 | {fail_if_no_peer_cert, true} 15 | ]}, 16 | {cluster_nodes, 17 | {[ 18 | "node1", 19 | "node2" 20 | ], disc}} 21 | ]} 22 | ]. 23 | -------------------------------------------------------------------------------- /tests/files/encode_toml_table_grafana.in: -------------------------------------------------------------------------------- 1 | verbose_logging: false 2 | 3 | servers: 4 | - host: 127.0.0.1 5 | port: 389 6 | use_ssl: no 7 | start_tls: no 8 | ssl_skip_verify: no 9 | bind_dn: cn=admin,dc=grafana,dc=org 10 | bind_password: grafana 11 | search_filter: (cn=%s) 12 | search_base_dns: 13 | - dc=grafana,dc=org 14 | - attributes: 15 | name: givenName 16 | surname: sn 17 | username: cn 18 | member_of: memberOf 19 | email: email 20 | - group_mappings: 21 | - group_dn: cn=admins,dc=grafana,dc=org 22 | org_role: Admin 23 | - group_dn: cn=users,dc=grafana,dc=org 24 | org_role: Editor 25 | - group_dn: "*" 26 | org_role: Viewer 27 | -------------------------------------------------------------------------------- /tests/files/encode_toml_table_grafana.out: -------------------------------------------------------------------------------- 1 | verbose_logging = false 2 | 3 | [[servers]] 4 | bind_dn = "cn=admin,dc=grafana,dc=org" 5 | bind_password = "grafana" 6 | host = "127.0.0.1" 7 | port = 389 8 | search_base_dns = ["dc=grafana,dc=org"] 9 | search_filter = "(cn=%s)" 10 | ssl_skip_verify = false 11 | start_tls = false 12 | use_ssl = false 13 | 14 | [servers.attributes] 15 | email = "email" 16 | member_of = "memberOf" 17 | name = "givenName" 18 | surname = "sn" 19 | username = "cn" 20 | 21 | [[servers.group_mappings]] 22 | group_dn = "cn=admins,dc=grafana,dc=org" 23 | org_role = "Admin" 24 | 25 | [[servers.group_mappings]] 26 | group_dn = "cn=users,dc=grafana,dc=org" 27 | org_role = "Editor" 28 | 29 | [[servers.group_mappings]] 30 | group_dn = "*" 31 | org_role = "Viewer" 32 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 1.8 3 | envlist = py{27}-ansible{24,25,26,27,28,29,210,211},py{35,36,37,38,39}-ansible{25,26,27,28,29,210,211} 4 | skipsdist = true 5 | 6 | [travis:env] 7 | ANSIBLE= 8 | 2.4: ansible24 9 | 2.5: ansible25 10 | 2.6: ansible26 11 | 2.7: ansible27 12 | 2.8: ansible28 13 | 2.9: ansible29 14 | 2.10: ansible210 15 | 2.11: ansible211 16 | 17 | [testenv] 18 | passenv = * 19 | deps = 20 | -rtest-requirements.txt 21 | ansible24: ansible<2.5 22 | ansible25: ansible<2.6 23 | ansible26: ansible<2.7 24 | ansible27: ansible<2.8 25 | ansible28: ansible<2.9 26 | ansible29: ansible<2.10 27 | ansible210: ansible<2.11 28 | ansible211: ansible<2.12 29 | commands = 30 | flake8 31 | {posargs:python -m unittest -v tests.test_config_encoders} 32 | -------------------------------------------------------------------------------- /meta/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | galaxy_info: 4 | author: Jiri Tyr 5 | description: Config Encoder Filters 6 | license: GPLv3 7 | min_ansible_version: 2.4 8 | platforms: 9 | - name: Amazon 10 | versions: 11 | - all 12 | - name: Debian 13 | versions: 14 | - all 15 | - name: EL 16 | versions: 17 | - all 18 | - name: eos 19 | versions: 20 | - all 21 | - name: Fedora 22 | versions: 23 | - all 24 | - name: FreeBSD 25 | versions: 26 | - all 27 | - name: GenericBSD 28 | versions: 29 | - all 30 | - name: GenericLinux 31 | versions: 32 | - all 33 | - name: GenericUNIX 34 | versions: 35 | - all 36 | - name: IOS 37 | versions: 38 | - all 39 | - name: Junos 40 | versions: 41 | - all 42 | - name: NXOS 43 | versions: 44 | - all 45 | - name: opensuse 46 | versions: 47 | - all 48 | - name: SLES 49 | versions: 50 | - all 51 | - name: SmartOS 52 | versions: 53 | - all 54 | - name: Solaris 55 | versions: 56 | - all 57 | - name: Ubuntu 58 | versions: 59 | - all 60 | galaxy_tags: 61 | - filter 62 | dependencies: [] 63 | -------------------------------------------------------------------------------- /tests/test_config_encoders.include: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Load input file for {{ item.encoder }}_{{ item.out | default(item.in) }} 4 | include_vars: 5 | file: files/{{ item.encoder }}_{{ item.in }}.in 6 | name: myinput 7 | 8 | - name: Comparison for {{ item.encoder }}_{{ item.out | default(item.in) }} 9 | assert: 10 | that: > 11 | "{{ ( 12 | myinput | encode_apache(**item.params | default({})) 13 | if item.encoder == 'encode_apache' else 14 | myinput | encode_erlang(**item.params | default({})) 15 | if item.encoder == 'encode_erlang' else 16 | myinput | encode_haproxy(**item.params | default({})) 17 | if item.encoder == 'encode_haproxy' else 18 | myinput | encode_ini(**item.params | default({})) 19 | if item.encoder == 'encode_ini' else 20 | myinput | encode_json(**item.params | default({})) 21 | if item.encoder == 'encode_json' else 22 | myinput | encode_logstash(**item.params | default({})) 23 | if item.encoder == 'encode_logstash' else 24 | myinput | encode_lua(**item.params | default({})) 25 | if item.encoder == 'encode_lua' else 26 | myinput | encode_nginx(**item.params | default({})) 27 | if item.encoder == 'encode_nginx' else 28 | myinput | encode_pam(**item.params | default({})) 29 | if item.encoder == 'encode_pam' else 30 | myinput | encode_toml(**item.params | default({})) 31 | if item.encoder == 'encode_toml' else 32 | myinput | encode_xml(**item.params | default({})) 33 | if item.encoder == 'encode_xml' else 34 | myinput | encode_yaml(**item.params | default({})) 35 | ) | trim | hash('md5') }}" == "{{ lookup('file', 'files/{{ item.encoder }}_{{ item.out | default(item.in) }}.out') | trim | hash('md5') }}" 36 | -------------------------------------------------------------------------------- /tests/test_config_encoders.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: all 4 | connection: local 5 | gather_facts: no 6 | vars: 7 | tests: 8 | # Apache 9 | - encoder: encode_apache 10 | in: boolean 11 | - encoder: encode_apache 12 | in: boolean 13 | out: boolean_convert 14 | params: 15 | convert_bools: yes 16 | - encoder: encode_apache 17 | in: boolean 18 | out: boolean_convert 19 | params: 20 | convert_bools: yes 21 | - encoder: encode_apache 22 | in: number 23 | - encoder: encode_apache 24 | in: number 25 | out: number_convert 26 | params: 27 | convert_nums: yes 28 | - encoder: encode_apache 29 | in: number 30 | out: number_quote 31 | params: 32 | quote_all_nums: yes 33 | - encoder: encode_apache 34 | in: string 35 | - encoder: encode_apache 36 | in: string 37 | out: string_quote 38 | params: 39 | quote_all_strings: yes 40 | - encoder: encode_apache 41 | in: vhost 42 | 43 | # JSON 44 | - encoder: encode_json 45 | in: boolean 46 | - encoder: encode_json 47 | in: boolean 48 | out: boolean_convert 49 | params: 50 | convert_bools: yes 51 | - encoder: encode_json 52 | in: string 53 | - encoder: encode_json 54 | in: number 55 | - encoder: encode_json 56 | in: number 57 | out: number_convert 58 | params: 59 | convert_nums: yes 60 | - encoder: encode_json 61 | in: list 62 | - encoder: encode_json 63 | in: list 64 | out: list_indent 65 | params: 66 | indent: " " 67 | - encoder: encode_json 68 | in: dict 69 | 70 | # YAML 71 | - encoder: encode_yaml 72 | in: boolean 73 | - encoder: encode_yaml 74 | in: boolean 75 | out: boolean_convert 76 | params: 77 | convert_bools: yes 78 | - encoder: encode_yaml 79 | in: string 80 | - encoder: encode_yaml 81 | in: number 82 | - encoder: encode_yaml 83 | in: number 84 | out: number_convert 85 | params: 86 | convert_nums: yes 87 | - encoder: encode_yaml 88 | in: list 89 | - encoder: encode_yaml 90 | in: list 91 | out: list_indent 92 | params: 93 | indent: " " 94 | - encoder: encode_yaml 95 | in: dict 96 | roles: 97 | - config_encoder_filters 98 | tasks: 99 | - include: test_config_encoders.include 100 | with_items: "{{ tests }}" 101 | -------------------------------------------------------------------------------- /tests/test_config_encoders.py: -------------------------------------------------------------------------------- 1 | import filter_plugins.config_encoders as CE 2 | import os 3 | import unittest 4 | import yaml 5 | 6 | 7 | class MyTestCase(unittest.TestCase): 8 | def _load_file(self, kind, encoder, test): 9 | dir_path = os.path.dirname(os.path.realpath(__file__)) 10 | 11 | f = open('%s/files/%s_%s.%s' % (dir_path, encoder, test, kind), 'r') 12 | 13 | if kind == 'in': 14 | ret = yaml.load(f) 15 | else: 16 | ret = f.read() 17 | 18 | f.close() 19 | 20 | return ret 21 | 22 | def _load_input(self, test): 23 | return self._load_file('in', self._encoder, test) 24 | 25 | def _load_output(self, test): 26 | return self._load_file('out', self._encoder, test) 27 | 28 | def _test(self, test, **params): 29 | test_in = test 30 | test_out = test 31 | 32 | if isinstance(test, list): 33 | test_in = test[0] 34 | test_out = test[1] 35 | 36 | my_in = self._load_input(test_in) 37 | my_out = self._load_output(test_out) 38 | 39 | encoder = getattr(CE, self._encoder) 40 | 41 | self.assertEqual(encoder(my_in, **params), my_out) 42 | 43 | 44 | class TestApache(MyTestCase): 45 | _encoder = 'encode_apache' 46 | 47 | def test_boolean(self): 48 | self._test('boolean') 49 | 50 | def test_boolean_convert(self): 51 | self._test(['boolean', 'boolean_convert'], convert_bools=True) 52 | 53 | def test_number(self): 54 | self._test('number') 55 | 56 | def test_number_convert(self): 57 | self._test(['number', 'number_convert'], convert_nums=True) 58 | 59 | def test_number_quote(self): 60 | self._test(['number', 'number_quote'], quote_all_nums=True) 61 | 62 | def test_string(self): 63 | self._test('string') 64 | 65 | def test_string_quote(self): 66 | self._test(['string', 'string_quote'], quote_all_strings=True) 67 | 68 | def test_vhost(self): 69 | self._test('vhost') 70 | 71 | 72 | class TestErlang(MyTestCase): 73 | _encoder = 'encode_erlang' 74 | 75 | def test_atom(self): 76 | self._test('atom') 77 | 78 | def test_boolean(self): 79 | self._test('boolean') 80 | 81 | def test_boolean_convert(self): 82 | self._test(['boolean', 'boolean_convert'], convert_bools=True) 83 | 84 | def test_number(self): 85 | self._test('number') 86 | 87 | def test_number_convert(self): 88 | self._test(['number', 'number_convert'], convert_nums=True) 89 | 90 | def test_string(self): 91 | self._test('string') 92 | 93 | def test_mixed(self): 94 | self._test('mixed') 95 | 96 | 97 | class TestIni(MyTestCase): 98 | _encoder = 'encode_ini' 99 | 100 | def test_general(self): 101 | self._test('general') 102 | 103 | def test_section(self): 104 | self._test('section') 105 | 106 | def test_mixed(self): 107 | self._test('mixed') 108 | 109 | def test_mixed_comment(self): 110 | self._test(['mixed', 'mixed_comment'], section_is_comment=True) 111 | 112 | def test_mixed_delimiter(self): 113 | self._test(['mixed', 'mixed_delimiter'], delimiter=" = ") 114 | 115 | def test_mixed_indent(self): 116 | self._test(['mixed', 'mixed_indent'], indent="\t") 117 | 118 | def test_mixed_quote(self): 119 | self._test(['mixed', 'mixed_quote'], quote='"') 120 | 121 | def test_mixed_ucase(self): 122 | self._test(['mixed', 'mixed_ucase'], ucase_prop=True) 123 | 124 | def test_null(self): 125 | self._test('null') 126 | 127 | 128 | class TestJson(MyTestCase): 129 | _encoder = 'encode_json' 130 | 131 | def test_boolean(self): 132 | self._test('boolean') 133 | 134 | def test_boolean_convert(self): 135 | self._test(['boolean', 'boolean_convert'], convert_bools=True) 136 | 137 | def test_string(self): 138 | self._test('string') 139 | 140 | def test_number(self): 141 | self._test('number') 142 | 143 | def test_number_convert(self): 144 | self._test(['number', 'number_convert'], convert_nums=True) 145 | 146 | def test_list(self): 147 | self._test('list') 148 | 149 | def test_list_indent(self): 150 | self._test(['list', 'list_indent'], indent=" ") 151 | 152 | def test_dict(self): 153 | self._test('dict') 154 | 155 | 156 | class TestLua(MyTestCase): 157 | _encoder = "encode_lua" 158 | 159 | def test_boolean(self): 160 | self._test('boolean') 161 | 162 | def test_boolean_convert(self): 163 | self._test(['boolean', 'boolean_convert'], convert_bools=True) 164 | 165 | def test_string(self): 166 | self._test('string') 167 | 168 | def test_number(self): 169 | self._test('number') 170 | 171 | def test_number_convert(self): 172 | self._test(['number', 'number_convert'], convert_nums=True) 173 | 174 | def test_list(self): 175 | self._test('list') 176 | 177 | def test_list_indent(self): 178 | self._test(['list', 'list_indent'], indent=" ") 179 | 180 | def test_dict(self): 181 | self._test('dict') 182 | 183 | 184 | class TestToml(MyTestCase): 185 | _encoder = 'encode_toml' 186 | 187 | def test_boolean(self): 188 | self._test('boolean') 189 | 190 | def test_boolean_convert(self): 191 | self._test(['boolean', 'boolean_convert'], convert_bools=True) 192 | 193 | def test_string(self): 194 | self._test('string') 195 | 196 | def test_string_quote(self): 197 | self._test(['string', 'string_quote'], quote="'") 198 | 199 | def test_number(self): 200 | self._test('number') 201 | 202 | def test_number_convert(self): 203 | self._test(['number', 'number_convert'], convert_nums=True) 204 | 205 | def test_array(self): 206 | self._test('array') 207 | 208 | def test_table(self): 209 | self._test('table') 210 | 211 | def test_table_array(self): 212 | self._test('table_array') 213 | 214 | def test_table_grafana(self): 215 | self._test('table_grafana') 216 | 217 | 218 | class TestXml(MyTestCase): 219 | _encoder = 'encode_xml' 220 | 221 | def test_element(self): 222 | self._test('element') 223 | 224 | def test_attribute(self): 225 | self._test('attribute') 226 | 227 | 228 | class TestYaml(MyTestCase): 229 | _encoder = 'encode_yaml' 230 | 231 | def test_boolean(self): 232 | self._test('boolean') 233 | 234 | def test_boolean_convert(self): 235 | self._test(['boolean', 'boolean_convert'], convert_bools=True) 236 | 237 | def test_string(self): 238 | self._test('string') 239 | 240 | def test_string_quote(self): 241 | self._test(['string', 'string_quote'], quote='') 242 | 243 | def test_number(self): 244 | self._test('number') 245 | 246 | def test_number_convert(self): 247 | self._test(['number', 'number_convert'], convert_nums=True) 248 | 249 | def test_list(self): 250 | self._test('list') 251 | 252 | def test_list_indent(self): 253 | self._test(['list', 'list_indent'], indent=" ") 254 | 255 | def test_dict(self): 256 | self._test('dict') 257 | 258 | def test_block(self): 259 | self._test('block') 260 | 261 | def test_null(self): 262 | self._test('null') 263 | 264 | 265 | if __name__ == '__main__': 266 | unittest.main() 267 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | 676 | -------------------------------------------------------------------------------- /filter_plugins/config_encoders.py: -------------------------------------------------------------------------------- 1 | # (c) 2016, Jiri Tyr 2 | # 3 | # This file is part of Config Encoder Filters (CEF) 4 | # 5 | # CEF is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # CEF is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with CEF. If not, see . 17 | 18 | """ 19 | Config Encoder Filters 20 | 21 | More information: https://github.com/jtyr/ansible-config_encoder_filters 22 | """ 23 | 24 | from __future__ import (absolute_import, division, print_function) 25 | from ansible.module_utils.six import string_types 26 | from ansible import errors 27 | from copy import copy 28 | import re 29 | 30 | 31 | def _str_is_bool(data): 32 | """Verify if data is boolean.""" 33 | 34 | return re.match(r"^(true|false)$", str(data), flags=re.IGNORECASE) 35 | 36 | 37 | def _str_is_int(data): 38 | """Verify if data is integer.""" 39 | 40 | return re.match(r"^[-+]?(0|[1-9][0-9]*)$", str(data)) 41 | 42 | 43 | def _str_is_float(data): 44 | """Verify if data is float.""" 45 | 46 | return re.match( 47 | r"^[-+]?(0|[1-9][0-9]*)(\.[0-9]*)?(e[-+]?[0-9]+)?$", 48 | str(data), flags=re.IGNORECASE) 49 | 50 | 51 | def _str_is_num(data): 52 | """Verify if data is either integer or float.""" 53 | 54 | return _str_is_int(data) or _str_is_float(data) 55 | 56 | 57 | def _is_num(data): 58 | """Verify if data is either int or float. 59 | 60 | Could be replaced by: 61 | 62 | from numbers import Number as number 63 | isinstance(data, number) 64 | 65 | but that requires Python v2.6+. 66 | """ 67 | 68 | return isinstance(data, int) or isinstance(data, float) 69 | 70 | 71 | def _escape(data, quote='"', format=None): 72 | """Escape special characters in a string.""" 73 | 74 | if format == 'xml': 75 | return ( 76 | str(data). 77 | replace('&', '&'). 78 | replace('<', '<'). 79 | replace('>', '>')) 80 | elif format == 'control': 81 | return ( 82 | str(data). 83 | replace('\b', '\\b'). 84 | replace('\f', '\\f'). 85 | replace('\n', '\\n'). 86 | replace('\r', '\\r'). 87 | replace('\t', '\\t')) 88 | elif quote is not None and len(quote): 89 | return str(data).replace('\\', '\\\\').replace(quote, "\\%s" % quote) 90 | else: 91 | return data 92 | 93 | 94 | def encode_apache( 95 | data, block_type='sections', convert_bools=False, convert_nums=False, 96 | indent=" ", level=0, quote_all_nums=False, quote_all_strings=False): 97 | """Convert Python data structure to Apache format.""" 98 | 99 | # Return value 100 | rv = "" 101 | 102 | if block_type == 'sections': 103 | for c in data['content']: 104 | # First check if this section has options 105 | if 'options' in c: 106 | rv += encode_apache( 107 | c['options'], 108 | convert_bools=convert_bools, 109 | convert_nums=convert_nums, 110 | indent=indent, 111 | level=level+1, 112 | quote_all_nums=quote_all_nums, 113 | quote_all_strings=quote_all_strings, 114 | block_type='options') 115 | 116 | is_empty = False 117 | 118 | # Check if this section has some sub-sections 119 | if 'sections' in c: 120 | for s in c['sections']: 121 | # Check for empty sub-sections 122 | for i in s['content']: 123 | if ( 124 | ('options' in i and len(i['options']) > 0) or 125 | ('sections' in i and len(i['sections']) > 0)): 126 | is_empty = True 127 | 128 | if is_empty: 129 | rv += "%s<%s" % (indent * level, s['name']) 130 | 131 | if 'operator' in s: 132 | rv += " %s" % s['operator'] 133 | 134 | if 'param' in s: 135 | rv += ' ' 136 | rv += encode_apache( 137 | s['param'], 138 | convert_bools=convert_bools, 139 | convert_nums=convert_nums, 140 | indent=indent, 141 | level=level+1, 142 | quote_all_nums=quote_all_nums, 143 | quote_all_strings=quote_all_strings, 144 | block_type='value') 145 | 146 | rv += ">\n" 147 | rv += encode_apache( 148 | s, 149 | convert_bools=convert_bools, 150 | convert_nums=convert_nums, 151 | indent=indent, 152 | level=level+1, 153 | quote_all_nums=quote_all_nums, 154 | quote_all_strings=quote_all_strings, 155 | block_type='sections') 156 | rv += "%s\n" % (indent * level, s['name']) 157 | 158 | # If not last item of the loop 159 | if c['sections'][-1] != s: 160 | rv += "\n" 161 | 162 | if ( 163 | data['content'][-1] != c and ( 164 | 'options' in c and len(c['options']) > 0 or ( 165 | 'sections' in c and 166 | len(c['sections']) > 0 and 167 | is_empty))): 168 | rv += "\n" 169 | 170 | elif block_type == 'options': 171 | for o in data: 172 | for key, val in sorted(o.items()): 173 | rv += "%s%s " % (indent * (level-1), key) 174 | rv += encode_apache( 175 | val, 176 | convert_bools=convert_bools, 177 | convert_nums=convert_nums, 178 | indent=indent, 179 | level=level+1, 180 | quote_all_nums=quote_all_nums, 181 | quote_all_strings=quote_all_strings, 182 | block_type='value') 183 | rv += "\n" 184 | 185 | elif block_type == 'value': 186 | if isinstance(data, bool) or convert_bools and _str_is_bool(data): 187 | # Value is a boolean 188 | 189 | rv += str(data).lower() 190 | 191 | elif ( 192 | _is_num(data) or 193 | (convert_nums and _str_is_num(data))): 194 | # Value is a number 195 | 196 | if quote_all_nums: 197 | rv += '"%s"' % data 198 | else: 199 | rv += str(data) 200 | 201 | elif isinstance(data, string_types): 202 | # Value is a string 203 | if ( 204 | quote_all_strings or 205 | " " in data or 206 | "\t" in data or 207 | "\n" in data or 208 | "\r" in data or 209 | data == ""): 210 | 211 | rv += '"%s"' % _escape(data) 212 | else: 213 | rv += data 214 | 215 | elif isinstance(data, list): 216 | # Value is a list 217 | for v in data: 218 | rv += encode_apache( 219 | v, 220 | convert_bools=convert_bools, 221 | convert_nums=convert_nums, 222 | indent=indent, 223 | level=level+1, 224 | quote_all_nums=quote_all_nums, 225 | quote_all_strings=quote_all_strings, 226 | block_type='value') 227 | 228 | # If not last item of the loop 229 | if data[-1] != v: 230 | rv += " " 231 | 232 | return rv 233 | 234 | 235 | def encode_erlang( 236 | data, atom_value_indicator=":", convert_bools=False, 237 | convert_nums=False, indent=" ", level=0, ordered_tuple_indicator=":"): 238 | """Convert Python data structure to Erlang format.""" 239 | 240 | # Return value 241 | rv = "" 242 | 243 | if isinstance(data, dict): 244 | # It's a dict 245 | 246 | rv += "\n" 247 | 248 | for key, val in sorted(data.items()): 249 | if key == ordered_tuple_indicator: 250 | rv += "%s{" % (indent*level) 251 | 252 | if isinstance(val, list): 253 | for i, v in enumerate(val): 254 | rv += encode_erlang( 255 | v, 256 | atom_value_indicator=atom_value_indicator, 257 | convert_bools=convert_bools, 258 | convert_nums=convert_nums, 259 | indent=indent, 260 | level=level+1, 261 | ordered_tuple_indicator=ordered_tuple_indicator) 262 | 263 | if i+1 < len(val): 264 | rv += ", " 265 | else: 266 | rv += "%s{%s," % (indent*level, key) 267 | 268 | if not isinstance(val, dict): 269 | rv += " " 270 | 271 | rv += encode_erlang( 272 | val, 273 | atom_value_indicator=atom_value_indicator, 274 | convert_bools=convert_bools, 275 | convert_nums=convert_nums, 276 | indent=indent, 277 | level=level+1, 278 | ordered_tuple_indicator=ordered_tuple_indicator) 279 | 280 | rv += "}" 281 | elif ( 282 | data == "null" or 283 | _is_num(data) or 284 | isinstance(data, bool) or 285 | (convert_nums and _str_is_num(data)) or 286 | (convert_bools and _str_is_bool(data))): 287 | # It's null, number or boolean 288 | 289 | rv += str(data).lower() 290 | 291 | elif isinstance(data, string_types): 292 | # It's a string 293 | 294 | atom_len = len(atom_value_indicator) 295 | 296 | if ( 297 | len(data) > atom_len and 298 | data[0:atom_len] == atom_value_indicator): 299 | 300 | # Atom configuration value 301 | rv += data[atom_len:] 302 | else: 303 | rv += '"%s"' % _escape(data) 304 | 305 | else: 306 | # It's a list 307 | 308 | rv += "[" 309 | 310 | for val in data: 311 | if ( 312 | isinstance(val, string_types) or 313 | _is_num(val)): 314 | rv += "\n%s" % (indent*level) 315 | 316 | rv += encode_erlang( 317 | val, 318 | atom_value_indicator=atom_value_indicator, 319 | convert_bools=convert_bools, 320 | convert_nums=convert_nums, 321 | indent=indent, 322 | level=level+1, 323 | ordered_tuple_indicator=ordered_tuple_indicator) 324 | 325 | if data[-1] == val: 326 | # Last item of the loop 327 | rv += "\n" 328 | else: 329 | rv += "," 330 | 331 | if len(data) > 0: 332 | rv += "%s]" % (indent * (level-1)) 333 | else: 334 | rv += "]" 335 | 336 | if level == 0: 337 | rv += ".\n" 338 | 339 | return rv 340 | 341 | 342 | def encode_haproxy(data, indent=" "): 343 | """Convert Python data structure to HAProxy format.""" 344 | 345 | # Return value 346 | rv = "" 347 | # Indicates first loop 348 | first = True 349 | # Indicates whether the previous section was a comment 350 | prev_comment = False 351 | 352 | for section in data: 353 | if first: 354 | first = False 355 | elif prev_comment: 356 | prev_comment = False 357 | else: 358 | # Print empty line between sections 359 | rv += "\n" 360 | 361 | if isinstance(section, dict): 362 | # It's a section 363 | rv += "%s\n" % list(section.keys())[0] 364 | 365 | # Process all parameters of the section 366 | for param in list(section.values())[0]: 367 | if isinstance(param, dict): 368 | for p_val in list(param.values())[0]: 369 | if len(p_val) > 0: 370 | rv += "%s%s %s\n" % ( 371 | indent, list(param.keys())[0], p_val) 372 | else: 373 | if len(param) > 0: 374 | rv += "%s%s\n" % (indent, param) 375 | else: 376 | # It's a comment of a parameter 377 | rv += "%s\n" % section 378 | prev_comment = True 379 | 380 | return rv 381 | 382 | 383 | def encode_ini( 384 | data, comment="#", delimiter="=", indent="", quote="", 385 | section_is_comment=False, ucase_prop=False): 386 | """Convert Python data structure to INI format.""" 387 | 388 | # Return value 389 | rv = "" 390 | 391 | # First process all standalone properties 392 | for prop, val in sorted(data.items()): 393 | if ucase_prop: 394 | prop = prop.upper() 395 | 396 | vals = [] 397 | 398 | if isinstance(val, list): 399 | vals = val 400 | elif not isinstance(val, dict): 401 | vals = [val] 402 | 403 | for item in vals: 404 | if ( 405 | len(quote) == 0 and 406 | isinstance(item, string_types) and 407 | len(item) == 0): 408 | item = '""' 409 | 410 | if item is not None: 411 | if item == "!!!null": 412 | rv += "%s%s\n" % (indent, prop) 413 | else: 414 | rv += "%s%s%s%s%s%s\n" % ( 415 | indent, prop, delimiter, quote, _escape(item, quote), 416 | quote) 417 | 418 | # Then process all sections 419 | for section, props in sorted(data.items()): 420 | if isinstance(props, dict): 421 | if rv != "": 422 | rv += "\n" 423 | 424 | if section_is_comment: 425 | rv += "%s %s\n" % (comment, section) 426 | else: 427 | rv += "[%s]\n" % (section) 428 | 429 | # Let process all section options as standalone properties 430 | rv += encode_ini( 431 | props, 432 | delimiter=delimiter, 433 | indent=indent, 434 | quote=quote, 435 | section_is_comment=section_is_comment, 436 | ucase_prop=ucase_prop) 437 | 438 | return rv 439 | 440 | 441 | def encode_json( 442 | data, convert_bools=False, convert_nums=False, indent=" ", level=0): 443 | """Convert Python data structure to JSON format.""" 444 | 445 | # Return value 446 | rv = "" 447 | 448 | if isinstance(data, dict): 449 | # It's a dict 450 | 451 | rv += "{" 452 | 453 | if len(data) > 0: 454 | rv += "\n" 455 | 456 | items = sorted(data.items()) 457 | 458 | for key, val in items: 459 | rv += '%s"%s": ' % (indent * (level+1), key) 460 | rv += encode_json( 461 | val, 462 | convert_bools=convert_bools, 463 | convert_nums=convert_nums, 464 | indent=indent, 465 | level=level+1) 466 | 467 | # Last item of the loop 468 | if items[-1] == (key, val): 469 | rv += "\n" 470 | else: 471 | rv += ",\n" 472 | 473 | if len(data) > 0: 474 | rv += "%s}" % (indent * level) 475 | else: 476 | rv += "}" 477 | 478 | if level == 0: 479 | rv += "\n" 480 | 481 | elif ( 482 | data == "null" or 483 | _is_num(data) or 484 | (convert_nums and _str_is_num(data)) or 485 | (convert_bools and _str_is_bool(data))): 486 | # It's a number, null or boolean 487 | 488 | rv += str(data).lower() 489 | 490 | elif isinstance(data, string_types): 491 | # It's a string 492 | 493 | rv += '"%s"' % _escape(_escape(data), format='control') 494 | 495 | else: 496 | # It's a list 497 | 498 | rv += "[" 499 | 500 | if len(data) > 0: 501 | rv += "\n" 502 | 503 | for val in data: 504 | rv += indent * (level+1) 505 | rv += encode_json( 506 | val, 507 | convert_bools=convert_bools, 508 | convert_nums=convert_nums, 509 | indent=indent, 510 | level=level+1) 511 | 512 | # Last item of the loop 513 | if data[-1] == val: 514 | rv += "\n" 515 | else: 516 | rv += ",\n" 517 | 518 | if len(data) > 0: 519 | rv += "%s]" % (indent * level) 520 | else: 521 | rv += "]" 522 | 523 | return rv 524 | 525 | 526 | def encode_logstash( 527 | data, backslash_ignore_prefix='@@@', convert_bools=False, 528 | convert_nums=False, indent=" ", level=0, prevtype="", 529 | section_prefix=":"): 530 | """Convert Python data structure to Logstash format.""" 531 | 532 | # Return value 533 | rv = "" 534 | 535 | if isinstance(data, dict): 536 | # The item is a dict 537 | 538 | if prevtype in ('value', 'value_hash', 'array'): 539 | rv += "{\n" 540 | 541 | items = sorted(data.items()) 542 | 543 | for key, val in items: 544 | if key[0] == section_prefix: 545 | rv += "%s%s {\n" % (indent * level, key[1:]) 546 | rv += encode_logstash( 547 | val, 548 | convert_bools=convert_bools, 549 | convert_nums=convert_nums, 550 | indent=indent, 551 | level=level+1, 552 | prevtype='block') 553 | 554 | # Last item of the loop 555 | if items[-1] == (key, val): 556 | if ( 557 | isinstance(val, string_types) or 558 | _is_num(val) or 559 | isinstance(val, bool) or ( 560 | isinstance(val, dict) and 561 | val and 562 | list(val.keys())[0][0] != section_prefix)): 563 | rv += "\n%s}\n" % (indent * level) 564 | else: 565 | rv += "%s}\n" % (indent * level) 566 | else: 567 | rv += indent * level 568 | 569 | if prevtype == 'value_hash': 570 | rv += '"%s" => ' % key 571 | else: 572 | rv += "%s => " % key 573 | 574 | rv += encode_logstash( 575 | val, 576 | convert_bools=convert_bools, 577 | convert_nums=convert_nums, 578 | indent=indent, 579 | level=level+1, 580 | prevtype=( 581 | 'value_hash' if isinstance(val, dict) else 'value')) 582 | 583 | if ( 584 | items[-1] != (key, val) and ( 585 | isinstance(val, string_types) or 586 | _is_num(val) or 587 | isinstance(val, bool))): 588 | rv += "\n" 589 | 590 | if prevtype in ('value', 'value_hash', 'array'): 591 | rv += "\n%s}" % (indent * (level-1)) 592 | 593 | if prevtype in ('value', 'value_array'): 594 | rv += "\n" 595 | 596 | elif ( 597 | _is_num(data) or 598 | isinstance(data, bool) or 599 | (convert_nums and _str_is_num(data)) or 600 | (convert_bools and _str_is_bool(data))): 601 | # It's number or boolean 602 | 603 | rv += str(data).lower() 604 | 605 | elif isinstance(data, string_types): 606 | # It's a string 607 | 608 | if data.startswith(backslash_ignore_prefix): 609 | rv += "%s" % data[len(backslash_ignore_prefix):] 610 | else: 611 | rv += '"%s"' % _escape(data) 612 | 613 | else: 614 | # It's a list 615 | 616 | for val in data: 617 | if isinstance(val, dict) and list( 618 | val.keys())[0][0] == section_prefix: 619 | # Value is a block 620 | 621 | rv += encode_logstash( 622 | val, 623 | convert_bools=convert_bools, 624 | convert_nums=convert_nums, 625 | indent=indent, 626 | level=level, 627 | prevtype='block') 628 | else: 629 | # First item of the loop 630 | if data[0] == val: 631 | rv += "[\n" 632 | 633 | rv += indent * level 634 | rv += encode_logstash( 635 | val, 636 | convert_bools=convert_bools, 637 | convert_nums=convert_nums, 638 | indent=indent, 639 | level=level+1, 640 | prevtype='array') 641 | 642 | # Last item of the loop 643 | if data[-1] == val: 644 | rv += "\n%s]" % (indent * (level-1)) 645 | else: 646 | rv += ",\n" 647 | 648 | return rv 649 | 650 | 651 | def encode_lua( 652 | data, convert_bools=False, convert_nums=False, 653 | indent=' ', level=0, sort_keys=True): 654 | """Convert Python data structure to Lua format.""" 655 | 656 | # Return value 657 | rv = "" 658 | 659 | if ( 660 | _is_num(data) or 661 | (convert_nums and _str_is_num(data)) or 662 | (convert_bools and _str_is_bool(data))): 663 | # It's a number or boolean 664 | rv += str(data).lower() + ";" 665 | 666 | elif isinstance(data, string_types): 667 | if data == 'null': 668 | rv += "nil;" 669 | else: 670 | rv += '"%s";' % _escape(_escape(data), format="control") 671 | 672 | elif isinstance(data, list): 673 | rv += "{\n" 674 | 675 | for val in data: 676 | val = encode_lua( 677 | val, 678 | convert_bools=convert_bools, 679 | convert_nums=convert_nums, 680 | sort_keys=sort_keys, 681 | indent=indent, 682 | level=level + 1) 683 | rv += "%s%s\n" % (indent*level, val) 684 | 685 | rv += "%s}" % (indent*(level-1)) 686 | 687 | if level > 1: 688 | rv += ";" 689 | 690 | elif isinstance(data, dict): 691 | if level > 0: 692 | rv += "{\n" 693 | 694 | if sort_keys: 695 | items = sorted(data.items()) 696 | else: 697 | items = data.items() 698 | for key, val in items: 699 | val = encode_lua( 700 | val, 701 | convert_bools=convert_bools, 702 | convert_nums=convert_nums, 703 | sort_keys=sort_keys, 704 | indent=indent, 705 | level=level + 1) 706 | rv += "%s%s = %s\n" % (indent*level, key, val) 707 | 708 | if level > 0: 709 | rv += indent * (level - 1) + "}" 710 | 711 | if level > 1: 712 | rv += ";" 713 | else: 714 | raise errors.AnsibleFilterError( 715 | "Unexpected data type: %s" % (type(data))) 716 | 717 | return rv 718 | 719 | 720 | def encode_nginx( 721 | data, block_semicolon=False, indent=" ", level=0, semicolon=';', 722 | semicolon_ignore_postfix='!;'): 723 | """Convert Python data structure to Nginx format.""" 724 | 725 | # Return value 726 | rv = "" 727 | # Indicates the item type [section|line] 728 | item_type = "" 729 | 730 | for item in data: 731 | if isinstance(item, dict): 732 | # Section 733 | if item_type in ('section', 'line'): 734 | rv += "\n" 735 | 736 | rv += "%s%s {\n" % (level*indent, list(item.keys())[0]) 737 | rv += encode_nginx( 738 | list(item.values())[0], 739 | level=level+1, 740 | block_semicolon=block_semicolon, 741 | semicolon=semicolon, 742 | semicolon_ignore_postfix=semicolon_ignore_postfix) 743 | rv += "%s}%s\n" % ( 744 | level*indent, semicolon if block_semicolon else '') 745 | 746 | item_type = 'section' 747 | 748 | elif isinstance(item, string_types): 749 | # Normal line 750 | if item_type == 'section': 751 | rv += "\n" 752 | 753 | item_type = 'line' 754 | ignore_semicolon = False 755 | 756 | if item.endswith(semicolon_ignore_postfix): 757 | item = item[:-len(semicolon_ignore_postfix)] 758 | ignore_semicolon = True 759 | 760 | rv += "%s%s" % (level*indent, item) 761 | 762 | # Do not finish comments with semicolon 763 | if item.startswith("# ") or ignore_semicolon: 764 | rv += "\n" 765 | else: 766 | rv += "%s\n" % (semicolon) 767 | 768 | else: 769 | raise errors.AnsibleFilterError( 770 | "Unexpected data type: %s" % (type(item))) 771 | 772 | return rv 773 | 774 | 775 | def encode_pam( 776 | data, print_label=False, separate_types=True, separator=" "): 777 | """Convert Python data structure to PAM format.""" 778 | 779 | # Return value 780 | rv = "" 781 | # Remember previous type to make newline between type blocks 782 | prev_type = None 783 | 784 | for label, rule in sorted(data.items()): 785 | if separate_types: 786 | # Add extra newline to separate blocks of the same type 787 | if prev_type is not None and prev_type != rule['type']: 788 | rv += "\n" 789 | 790 | prev_type = rule['type'] 791 | 792 | if print_label: 793 | rv += "# %s\n" % label 794 | 795 | if 'service' in rule: 796 | rv += "%s%s" % (rule['service'], separator) 797 | 798 | if 'silent' in rule and rule['silent']: 799 | rv += '-' 800 | 801 | rv += "%s%s" % (rule['type'], separator) 802 | 803 | if isinstance(rule['control'], list): 804 | rv += "[%s]%s" % ( 805 | " ".join( 806 | map( 807 | lambda k: "=".join(map(str, k)), 808 | map(lambda x: list(x.items())[0], rule['control']))), 809 | separator) 810 | else: 811 | rv += "%s%s" % (rule['control'], separator) 812 | 813 | rv += rule['path'] 814 | 815 | if 'args' in rule and rule['args']: 816 | rv += separator 817 | 818 | for i, arg in enumerate(rule['args']): 819 | if i > 0: 820 | rv += ' ' 821 | 822 | if isinstance(arg, dict): 823 | rv += "=".join(map(str, list(arg.items())[0])) 824 | else: 825 | rv += arg 826 | 827 | rv += "\n" 828 | 829 | return rv 830 | 831 | 832 | def encode_toml( 833 | data, convert_bools=False, convert_nums=False, first=True, quote='"', 834 | table_name="", table_type=None): 835 | """Convert Python data structure to TOML format.""" 836 | 837 | # Return value 838 | rv = "" 839 | 840 | if isinstance(data, dict): 841 | # It's a dict 842 | 843 | tn = table_name 844 | 845 | # First process all keys with elementar value (num/str/bool/array) 846 | for k, v in sorted(data.items()): 847 | 848 | if not (isinstance(v, dict) or isinstance(v, list)): 849 | if tn: 850 | if not first: 851 | rv += "\n" 852 | 853 | if table_type == 'table': 854 | rv += "[%s]\n" % tn 855 | else: 856 | rv += "[[%s]]\n" % tn 857 | 858 | rv += "%s = %s\n" % ( 859 | k, 860 | encode_toml( 861 | v, 862 | convert_bools=convert_bools, 863 | convert_nums=convert_nums, 864 | first=first, 865 | quote=quote)) 866 | 867 | first = False 868 | tn = '' 869 | elif isinstance(v, list) and (not v or not isinstance(v[0], dict)): 870 | if tn: 871 | if not first: 872 | rv += "\n" 873 | 874 | if table_type == 'table': 875 | rv += "[%s]\n" % tn 876 | else: 877 | rv += "[[%s]]\n" % tn 878 | 879 | rv += "%s = %s\n" % ( 880 | k, 881 | encode_toml( 882 | v, 883 | convert_bools=convert_bools, 884 | convert_nums=convert_nums, 885 | first=first, 886 | quote=quote)) 887 | 888 | first = False 889 | tn = '' 890 | 891 | if not data and table_type is not None: 892 | if not first: 893 | rv += "\n" 894 | 895 | if table_type == 'table': 896 | rv += "[%s]\n" % tn 897 | else: 898 | rv += "[[%s]]\n" % tn 899 | 900 | # Then process tables and arrays of tables 901 | for k, v in sorted(data.items()): 902 | tn = table_name 903 | 904 | if isinstance(v, dict): 905 | # Table 906 | tk = k 907 | 908 | if '.' in k: 909 | tk = "%s%s%s" % (quote, _escape(k, quote), quote) 910 | 911 | if tn: 912 | tn += ".%s" % tk 913 | else: 914 | tn += "%s" % tk 915 | 916 | rv += encode_toml( 917 | v, 918 | convert_bools=convert_bools, 919 | convert_nums=convert_nums, 920 | first=first, 921 | quote=quote, 922 | table_name=tn, 923 | table_type='table') 924 | 925 | first = False 926 | elif isinstance(v, list) and (not v or isinstance(v[0], dict)): 927 | # Array of tables 928 | tk = k 929 | 930 | if '.' in k: 931 | tk = "%s%s%s" % (quote, _escape(k, quote), quote) 932 | 933 | if tn: 934 | tn += ".%s" % tk 935 | else: 936 | tn += "%s" % tk 937 | 938 | for t in v: 939 | rv += encode_toml( 940 | t, 941 | convert_bools=convert_bools, 942 | convert_nums=convert_nums, 943 | first=first, 944 | quote=quote, 945 | table_name=tn, 946 | table_type='table_array') 947 | 948 | first = False 949 | 950 | elif isinstance(data, list): 951 | 952 | # Check if all values are elementar (num/str/bool/array) 953 | def is_elem(a): 954 | all_elementar = True 955 | 956 | for lv in a: 957 | if ( 958 | isinstance(lv, dict) or ( 959 | isinstance(lv, list) and 960 | not is_elem(lv))): 961 | all_elementar = False 962 | break 963 | 964 | return all_elementar 965 | 966 | if is_elem(data): 967 | v_len = len(data) 968 | 969 | array = '' 970 | 971 | for i, lv in enumerate(data): 972 | array += "%s" % encode_toml( 973 | lv, 974 | convert_bools=convert_bools, 975 | convert_nums=convert_nums, 976 | first=first, 977 | quote=quote) 978 | 979 | if i+1 < v_len: 980 | array += ', ' 981 | 982 | rv += "[%s]" % (array) 983 | 984 | elif ( 985 | _is_num(data) or 986 | isinstance(data, bool) or 987 | (convert_nums and _str_is_num(data)) or 988 | (convert_bools and _str_is_bool(data))): 989 | # It's number or boolean 990 | 991 | rv += str(data).lower() 992 | 993 | elif isinstance(data, string_types): 994 | # It's a string 995 | 996 | rv += "%s%s%s" % (quote, _escape(data, quote), quote) 997 | 998 | return rv 999 | 1000 | 1001 | def encode_xml( 1002 | data, attribute_sign="^", escape_xml=True, indent=" ", level=0): 1003 | """Convert Python data structure to XML format.""" 1004 | 1005 | # Return value 1006 | rv = "" 1007 | 1008 | if isinstance(data, list): 1009 | # Pocess anything what's not attribute 1010 | for item in data: 1011 | if ( 1012 | not ( 1013 | isinstance(item, dict) and 1014 | list(item.keys())[0].startswith(attribute_sign))): 1015 | rv += encode_xml( 1016 | item, 1017 | attribute_sign=attribute_sign, 1018 | indent=indent, 1019 | level=level, 1020 | escape_xml=escape_xml) 1021 | elif isinstance(data, dict): 1022 | # It's eiher an attribute or an element 1023 | 1024 | key, val = list(data.items())[0] 1025 | 1026 | if key.startswith(attribute_sign): 1027 | # Process attribute 1028 | rv += ' %s="%s"' % (key[1:], _escape(val)) 1029 | else: 1030 | # Process element 1031 | rv = '%s<%s' % (level*indent, key) 1032 | 1033 | # Check if there are any attributes 1034 | if isinstance(val, list): 1035 | num_attrs = 0 1036 | 1037 | for item in val: 1038 | if ( 1039 | isinstance(item, dict) and 1040 | list(item.keys())[0].startswith(attribute_sign)): 1041 | num_attrs += 1 1042 | rv += encode_xml( 1043 | item, 1044 | attribute_sign=attribute_sign, 1045 | indent=indent, 1046 | level=level) 1047 | 1048 | if val == '' or (isinstance(val, list) and num_attrs == len(val)): 1049 | # Close the element as empty 1050 | rv += " />\n" 1051 | else: 1052 | # Close the element as normal 1053 | rv += ">" 1054 | 1055 | # Check if the value is text 1056 | val_not_text = False 1057 | 1058 | if isinstance(val, list): 1059 | # Check if it contains only attributes and a text value 1060 | for item in val: 1061 | if (isinstance(item, dict) and not list( 1062 | item.keys())[0].startswith(attribute_sign)): 1063 | val_not_text = True 1064 | break 1065 | elif isinstance(val, dict): 1066 | val_not_text = True 1067 | 1068 | if val_not_text: 1069 | rv += "\n" 1070 | 1071 | # Process inner content of the element 1072 | rv += encode_xml( 1073 | val, 1074 | attribute_sign=attribute_sign, 1075 | indent=indent, 1076 | level=level+1, 1077 | escape_xml=escape_xml) 1078 | 1079 | if val_not_text: 1080 | rv += level*indent 1081 | 1082 | rv += "\n" % key 1083 | else: 1084 | # It's a string 1085 | 1086 | rv += "%s" % _escape(data, format=('xml' if escape_xml else None)) 1087 | 1088 | return rv 1089 | 1090 | 1091 | def encode_yaml( 1092 | data, block_prefix=';;;', convert_bools=False, convert_nums=False, 1093 | indent=" ", level=0, quote='"', skip_indent=False): 1094 | """Convert Python data structure to YAML format.""" 1095 | 1096 | # Return value 1097 | rv = "" 1098 | 1099 | if isinstance(data, dict): 1100 | # It's a dictionary 1101 | 1102 | if len(data.keys()) == 0: 1103 | rv += "{}\n" 1104 | else: 1105 | for i, (key, val) in enumerate(sorted(data.items())): 1106 | # Skip indentation only for the first pair 1107 | rv += "%s%s:" % ( 1108 | "" if i == 0 and skip_indent else level*indent, key) 1109 | 1110 | if isinstance(val, dict) and len(val.keys()) == 0: 1111 | rv += " {}\n" 1112 | else: 1113 | if ( 1114 | isinstance(val, dict) or ( 1115 | isinstance(val, list) and 1116 | len(val) != 0)): 1117 | rv += "\n" 1118 | else: 1119 | rv += " " 1120 | 1121 | rv += encode_yaml( 1122 | val, 1123 | block_prefix=block_prefix, 1124 | convert_bools=convert_bools, 1125 | convert_nums=convert_nums, 1126 | indent=indent, 1127 | level=level+1, 1128 | quote=quote) 1129 | 1130 | elif isinstance(data, list): 1131 | # It's a list 1132 | 1133 | if len(data) == 0: 1134 | rv += "[]\n" 1135 | else: 1136 | for item in data: 1137 | if isinstance(item, list): 1138 | list_indent = "%s-\n" % (level*indent) 1139 | else: 1140 | list_indent = "%s- " % (level*indent) 1141 | 1142 | rv += "%s%s" % (list_indent, encode_yaml( 1143 | item, 1144 | block_prefix=block_prefix, 1145 | convert_bools=convert_bools, 1146 | convert_nums=convert_nums, 1147 | indent=indent, 1148 | level=level+1, 1149 | quote=quote, 1150 | skip_indent=True)) 1151 | 1152 | elif ( 1153 | data == "null" or 1154 | isinstance(data, bool) or 1155 | (convert_bools and _str_is_bool(data))): 1156 | # It's a boolean 1157 | 1158 | rv += "%s\n" % str(data).lower() 1159 | 1160 | elif ( 1161 | _is_num(data) or 1162 | (convert_nums and _str_is_num(data))): 1163 | # It's a number 1164 | 1165 | rv += "%s\n" % str(data) 1166 | 1167 | else: 1168 | # It's a string 1169 | 1170 | if data is None: 1171 | rv += "null\n" 1172 | elif data.startswith(block_prefix): 1173 | rv += "%s\n" % data[len(block_prefix):].replace( 1174 | "\n", "\n%s" % (level*indent)) 1175 | else: 1176 | rv += "%s%s%s\n" % (quote, _escape(data, quote), quote) 1177 | 1178 | return rv 1179 | 1180 | 1181 | def __eval_replace(match): 1182 | """Evaluate the real value of the variable specified as a string.""" 1183 | 1184 | ret = '__item' 1185 | ret += ''.join(match.groups()[1:]) 1186 | 1187 | # Try to evaluate the value of the special string 1188 | try: 1189 | ret = eval(ret) 1190 | except Exception: 1191 | # Return empty string if something went wrong 1192 | ret = '' 1193 | 1194 | return str(ret) 1195 | 1196 | 1197 | def template_replace(data, replacement): 1198 | """Replace special template decorated variable with its real value.""" 1199 | 1200 | # Make the replacement variable visible for the __eval_replace function 1201 | global __item 1202 | __item = replacement 1203 | 1204 | # Clone the data to keep the original untouched 1205 | local_data = copy(data) 1206 | 1207 | # Walk through the data structure and try to replace all special strings 1208 | if isinstance(local_data, list): 1209 | local_data = map( 1210 | lambda x: template_replace(x, replacement), local_data) 1211 | elif isinstance(local_data, dict): 1212 | for key, val in local_data.items(): 1213 | local_data[key] = template_replace(val, replacement) 1214 | elif isinstance(local_data, string_types): 1215 | # Replace the special string by it's evaluated value 1216 | p = re.compile(r'\{\[\{\s*(\w+)([^}\s]+|)\s*\}\]\}') 1217 | local_data = p.sub(__eval_replace, local_data) 1218 | 1219 | return local_data 1220 | 1221 | 1222 | class FilterModule(object): 1223 | """Ansible encoder Jinja2 filters.""" 1224 | 1225 | def filters(self): 1226 | """Expose filters to ansible.""" 1227 | 1228 | return { 1229 | 'encode_apache': encode_apache, 1230 | 'encode_erlang': encode_erlang, 1231 | 'encode_haproxy': encode_haproxy, 1232 | 'encode_ini': encode_ini, 1233 | 'encode_json': encode_json, 1234 | 'encode_logstash': encode_logstash, 1235 | 'encode_lua': encode_lua, 1236 | 'encode_nginx': encode_nginx, 1237 | 'encode_pam': encode_pam, 1238 | 'encode_toml': encode_toml, 1239 | 'encode_xml': encode_xml, 1240 | 'encode_yaml': encode_yaml, 1241 | 'template_replace': template_replace, 1242 | } 1243 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Config Encoder Filters 2 | ====================== 3 | 4 | This is an Ansible role used to deliver the Config Encoder Filters as 5 | a dependency of another Ansible role. 6 | 7 | .. image:: https://travis-ci.org/jtyr/ansible-config_encoder_filters.svg?branch=master 8 | :target: https://travis-ci.org/jtyr/ansible-config_encoder_filters 9 | 10 | 11 | Table of Contents 12 | ----------------- 13 | 14 | - Motivation_ 15 | - Example_ 16 | - Usage_ 17 | - Installation_ 18 | - `Supported encoders`_ 19 | - encode_apache_ 20 | - encode_erlang_ 21 | - encode_haproxy_ 22 | - encode_ini_ 23 | - encode_json_ 24 | - encode_logstash_ 25 | - encode_lua_ 26 | - encode_nginx_ 27 | - encode_pam_ 28 | - encode_toml_ 29 | - encode_xml_ 30 | - encode_yaml_ 31 | - Utilities_ 32 | - template_replace_ 33 | - Testing_ 34 | - License_ 35 | - Author_ 36 | 37 | ---- 38 | 39 | 40 | .. _Motivation: 41 | 42 | Motivation 43 | ---------- 44 | 45 | Ansible Galaxy contains a lot of useful roles. Some of them exist in 46 | many variations which differ only by their parameterization. The 47 | parameterization is often used mainly in templates which generate the 48 | configuration file. A good example such issues are roles for Nginx of 49 | which you can find almost 200 in the Ansible Galaxy. 50 | 51 | Nginx is possible to configure in infinite number of ways and therefore 52 | is almost impossible to create an Ansible template file which would 53 | capture all possible variations of the configuration. Even if a suitable 54 | roles is found, users often want to customize even more. This is where 55 | people normally clone the role and add parameters they are missing. Some 56 | people try to get the change back into the original role by creating a 57 | pull request (PR) but sometimes such change is not accepted by the 58 | maintainer of the original role and the user ends up maintaining his/her 59 | own clone forever. 60 | 61 | This is why the Config Encoder filters were developed to facilitate the 62 | creation of Ansible roles with universal configuration. The structure of 63 | the configuration file is described as a YAML data structure stored in a 64 | variable. The variable together with he Config Encoder filter is then 65 | used in the template file which is used to generate the final 66 | configuration file. This approach allows to shift the paradigm of 67 | thinking about configuration files as templates to thinking about them as 68 | data structures. The data structure can be dynamically generated which 69 | allows to create truly universal configuration. 70 | 71 | 72 | .. _Example: 73 | 74 | Example 75 | ------- 76 | 77 | Imagine the following INI file: 78 | 79 | .. code:: ini 80 | 81 | [section1] 82 | option11=value11 83 | option12=value12 84 | 85 | Such configuration file can be described as a YAML data structure: 86 | 87 | .. code:: yaml 88 | 89 | myapp_config: 90 | section1: 91 | option11: value11 92 | option12: value12 93 | 94 | The variable is then used together with the ``encode_ini`` Config Encoder 95 | filter in the template file ``myapp.cfg.j2`` like this: 96 | 97 | .. code:: jinja2 98 | 99 | {{ myapp_config | encode_ini }} 100 | 101 | And finally, the template file is used in a task like this: 102 | 103 | .. code:: yaml 104 | 105 | - name: Create config file 106 | template: 107 | src: myapp.cfg.j2 108 | dest: /etc/myapp/myapp.cfg 109 | 110 | When the task is executed, it creates exactly the same file as the 111 | original INI file. 112 | 113 | So we can describe the configuration as a data structure which is then 114 | converted into the final configuration file format with the Config 115 | Encoder filter. 116 | 117 | In order to change the above configuration, we would have to overwrite 118 | the ``myapp_config`` which is not very practical. Therefore we break the 119 | monolithic variable into a set of variables which will allow us to change 120 | any part of the configuration without the need to overwrite the whole 121 | data structure: 122 | 123 | .. code:: yaml 124 | 125 | myapp_config_section1_option11: value11 126 | myapp_config_section1_option12: value12 127 | 128 | myapp_config_section1__default: 129 |  option11: "{{ myapp_config_section1_option11 }}" 130 |  option12: "{{ myapp_config_section1_option12 }}" 131 | 132 | myapp_config_section1__custom: {} 133 | 134 | myapp_config_default: 135 |  section1: "{{ 136 |    myapp_config_section1__default | combine( 137 | myapp_config_section1__custom) }}" 138 | 139 | myapp_config__custom: {} 140 | 141 | myapp_config: "{{ 142 |  myapp_config__default | combine( 143 | myapp_config__custom) }}" 144 | 145 | Like this, if we want to change the value of the ``option11``, we only 146 | override the variable ``myapp_config_section1_option11``: 147 | 148 | .. code:: yaml 149 | 150 | myapp_config_section1_option11: My new value 151 | 152 | If we want to add a new option into the ``section1``, we add it into the 153 | variable ``myapp_config_section1__custom`` which is then merged with the 154 | default list of options: 155 | 156 | .. code:: yaml 157 | 158 | myapp_config_section1__custom: 159 | section13: value13 160 | 161 | And if we want to add a new section, we add it into the variable 162 | ``myapp_config__custom`` which is then merged with the default list of 163 | sections: 164 | 165 | .. code:: yaml 166 | 167 | myapp_config__custom: 168 | section2: 169 | option21: value21 170 | 171 | The above is showing an example for INI configuration files only but the 172 | same principle is possible to use for all the supported Config Encoders 173 | listed bellow. 174 | 175 | 176 | .. _Usage: 177 | 178 | Usage 179 | ----- 180 | 181 | Config Encoder filters can be used in any Ansible role by adding the 182 | ``config_encoder_filters`` role into the list of dependencies in the 183 | ``meta/main.yml`` file: 184 | 185 | .. code:: yaml 186 | 187 | dependencies: 188 | - config_encoder_filters 189 | 190 | The usage directy from a Playbook requires to add the 191 | ``config_encoder_filters`` into the list of roles: 192 | 193 | .. code:: yaml 194 | 195 | - name: My test Play 196 | hosts: all 197 | roles: 198 | - config_encoder_filters 199 | tasks: 200 | - name: Create config file 201 | template: 202 | src: my.conf.j2 203 | dest: /tmp/my.conf 204 | 205 | 206 | .. _Installation: 207 | 208 | Installation 209 | ------------ 210 | 211 | The role can be downloaded either via Ansible Galaxy command: 212 | 213 | .. code:: shell 214 | 215 | $ ansible-galaxy install jtyr.config_encoder_filters,master,config_encoder_filters 216 | 217 | or via Ansible Gallaxy requirements file: 218 | 219 | .. code:: shell 220 | 221 | $ cat ./requirements.yaml 222 | --- 223 | 224 | - src: https://github.com/jtyr/ansible-config_encoder_filters.git 225 | name: config_encoder_filters 226 | $ ansible-galaxy -r ./requirements.yaml 227 | 228 | or via Git: 229 | 230 | .. code:: shell 231 | 232 | $ git clone https://github.com/jtyr/ansible-config_encoder_filters.git config_encoder_filters 233 | 234 | 235 | .. _`Supported encoders`: 236 | 237 | Supported encoders 238 | ------------------ 239 | 240 | The following is the list of supported Config Encoder filters. Each 241 | filter requires special data structure as its input. Each filter also has 242 | a set of parameters which can modify the behaviour of the filter. 243 | 244 | 245 | .. _encode-apache: 246 | 247 | encode_apache 248 | ^^^^^^^^^^^^^ 249 | 250 | This filter helps to create configuration in the format used by Apache 251 | web server. The expected data structure is the following: 252 | 253 | .. code:: yaml 254 | 255 | my_apache_vhost: 256 | content: 257 | - sections: 258 | - name: VirtualHost 259 | param: "*:80" 260 | content: 261 | - options: 262 | - DocumentRoot: /www/example1 263 | - ServerName: www.example.com 264 | - ErrorLog: /var/log/httpd/www.example.com-error_log 265 | - CustomLog: 266 | - /var/log/httpd/www.example.com-access_log 267 | - common 268 | - "#": Other directives here ... 269 | 270 | The variable starts with ``content`` which can contain list of 271 | ``sections`` or ``options``. ``sections`` then contain list of individual 272 | sections which has the ``name``, ``param`` and ``content`` parameter. The 273 | ``content`` can again contain a list of `sections`` or ``options``. 274 | 275 | The above variable can be used in the template file like this: 276 | 277 | .. code:: jinja2 278 | 279 | {{ my_apache_vhost | encode_apache }} 280 | 281 | The output of such template would be: 282 | 283 | .. code:: apache 284 | 285 | 286 | DocumentRoot /www/example1 287 | ServerName www.example.com 288 | ErrorLog /var/log/httpd/www.example.com-error_log 289 | CustomLog /var/log/httpd/www.example.com-access_log common 290 | # "Other directives here ..." 291 | 292 | 293 | The filter can have the following parameters: 294 | 295 | - ``convert_bools=false`` 296 | 297 | Indicates whether Boolean values presented as a string should be 298 | converted to a real Boolean value. For example ``var1: 'True'`` would 299 | be represented as a string but by using the ``convert_bools=true`` it 300 | will be converted into Boolean like it would be defined like ``var1: 301 | true``. 302 | 303 | - ``convert_nums=false`` 304 | 305 | Indicates whether number presented as a string should be converted to 306 | number. For example ``var1: '123'`` would be represented as a string 307 | but by using the ``convert_nums=true`` it will be converted it to a 308 | number like it would be defined like ``var1: 123``. It's also possible 309 | to use the YAML type casting to convert string to number (e.g. ``!!int 310 | "1234"``, ``!!float "3.14"``). 311 | 312 | - ``indent=" "`` 313 | 314 | Defines the indentation unit. 315 | 316 | - ``level=0`` 317 | 318 | Indicates the initial level of the indentation. Value ``0`` starts 319 | indenting from the beginning of the line. Setting the value to higher 320 | than ``0`` indents the content by ``indent * level``. 321 | 322 | - ``quote_all_nums=false`` 323 | 324 | Number values are not quoted by default. This parameter will force to 325 | quote all numbers. 326 | 327 | - ``quote_all_strings=false`` 328 | 329 | String values are quoted only if they contain a space. This parameter 330 | will force to quote all strings regardless if the they contain the 331 | space or not. 332 | 333 | 334 | .. _encode-erlang: 335 | 336 | encode_erlang 337 | ^^^^^^^^^^^^^ 338 | 339 | This filter helps to create configuration in the Erlang format. The 340 | expected data structure is the following: 341 | 342 | .. code:: yaml 343 | 344 | my_rabbitmq_config: 345 | - rabbit: 346 | - tcp_listeners: 347 | - '"127.0.0.1"': 5672 348 | - ssl_listeners: 349 | - 5671 350 | - ssl_options: 351 | - cacertfile: /path/to/testca/cacert.pem 352 | - certfile: /path/to/server/cert.pem 353 | - keyfile: /path/to/server/key.pem 354 | - verify: :verify_peer 355 | - fail_if_no_peer_cert: true 356 | - cluster_nodes: 357 | :: 358 | - 359 | - :"'rabbit@node1'" 360 | - :"'rabbit@node2'" 361 | - :disc 362 | 363 | The variable consists of a lists of dictionaries. The value of the key-value 364 | pair can be another list or simple value like a string or a number. Erlang 365 | tuples can be enforced by prepending the value with the special character 366 | specified in the ``atom_value_indicator``. Order in tuple can be achieved by 367 | using special construction as shown for the ``cluste_nodes`` tuple from the 368 | above example. The indicator starting this special construction can be set with 369 | the parameter ``ordered_tuple_indicator``. 370 | 371 | The above variable can be used in the template file like this: 372 | 373 | .. code:: jinja2 374 | 375 | {{ my_rabbitmq_config | encode_erlang }} 376 | 377 | The output of such template would be: 378 | 379 | .. code:: erlang 380 | 381 | [ 382 | {rabbit, [ 383 | {tcp_listeners, [ 384 | {"127.0.0.1", 5672} 385 | ]}, 386 | {ssl_listeners, [ 387 | 5671 388 | ]}, 389 | {ssl_options, [ 390 | {cacertfile, "/path/to/testca/cacert.pem"}, 391 | {certfile, "/path/to/server/cert.pem"}, 392 | {keyfile, "/path/to/server/key.pem"}, 393 | {verify, verify_peer}, 394 | {fail_if_no_peer_cert, true} 395 | ]}, 396 | {cluster_nodes, 397 | {[ 398 | 'rabbit@node1', 399 | 'rabbit@node2' 400 | ], disc}} 401 | ]} 402 | ]. 403 | 404 | The filter can have the following parameters: 405 | 406 | - ``atom_value_indicator=":"`` 407 | 408 | The value of this parameter indicates the string which must be 409 | prepended to a string value to treat it as an atom value. 410 | 411 | - ``convert_bools=false`` 412 | 413 | Indicates whether Boolean values presented as a string should be 414 | converted to a real Boolean value. For example ``var1: 'True'`` would 415 | be represented as a string but by using the ``convert_bools=true`` it 416 | will be converted into Boolean like it would be defined like ``var1: 417 | true``. 418 | 419 | - ``convert_nums=false`` 420 | 421 | Indicates whether number presented as a string should be converted to 422 | number. For example ``var1: '123'`` would be represented as a string 423 | but by using the ``convert_nums=true`` it will be converted it to a 424 | number like it would be defined like ``var1: 123``. It's also possible 425 | to use the YAML type casting to convert string to number (e.g. ``!!int 426 | "1234"``, ``!!float "3.14"``). 427 | 428 | - ``indent=" "`` 429 | 430 | Defines the indentation unit. 431 | 432 | - ``level=0`` 433 | 434 | Indicates the initial level of the indentation. Value ``0`` starts 435 | indenting from the beginning of the line. Setting the value to higher 436 | than ``0`` indents the content by ``indent * level``. 437 | 438 | - ``ordered_tuple_indicator=":"`` 439 | 440 | Indicator used to start the special construction with ordered tuple. 441 | 442 | 443 | .. _encode-haproxy: 444 | 445 | encode_haproxy 446 | ^^^^^^^^^^^^^^ 447 | 448 | This filter helps to create configuration in the format used in Haproxy. 449 | The expected data structure is the following: 450 | 451 | .. code:: yaml 452 | 453 | my_haproxy_config: 454 | - global: 455 | - daemon 456 | - maxconn 256 457 | - "# This is the default section" 458 | - defaults: 459 | - mode http 460 | - timeout: 461 | - connect 5000ms 462 | - client 50000ms 463 | - server 50000ms 464 | - frontend http-in: 465 | - "# This is the bind address/port" 466 | - bind *:80 467 | - default_backend servers 468 | - backend servers 469 | - server server1 127.0.0.1:8000 maxconn 32 470 | 471 | The variable is a list which can contain a simple string value or a dictionary 472 | which indicates a section. 473 | 474 | The above variable can be used in the template file like this: 475 | 476 | .. code:: jinja2 477 | 478 | {{ my_haproxy_config | encode_haproxy }} 479 | 480 | The output of such template would be: 481 | 482 | .. code:: haproxy 483 | 484 | global 485 | daemon 486 | maxconn 256 487 | 488 | # This is the default section 489 | defaults 490 | mode http 491 | timeout connect 5000ms 492 | timeout client 50000ms 493 | timeout server 50000ms 494 | 495 | frontend http-in 496 | # This is the bind address/port 497 | bind *:80 498 | default_backend servers 499 | backend servers 500 | server server1 127.0.0.1:8000 maxconn 32 501 | 502 | The filter can have the following parameters: 503 | 504 | - ``indent=" "`` 505 | 506 | Defines the indentation unit. 507 | 508 | 509 | .. _encode-ini: 510 | 511 | encode_ini 512 | ^^^^^^^^^^ 513 | 514 | This filter helps to create configuration in the INI format. The expected 515 | data structure is the following: 516 | 517 | .. code:: yaml 518 | 519 | my_rsyncd_config: 520 | uid: nobody 521 | gid: nobody 522 | use chroot: no 523 | max connections: 4 524 | syslog facility: local5 525 | pid file: /run/rsyncd.pid 526 | ftp: 527 | path: /srv/ftp 528 | comment: ftp area 529 | 530 | The variable consist of dictionaries which can be nested. If the value of the 531 | key-value pair on the first level is of a simple type (string, number, boolean), 532 | such pair is considered to be global and gets processed first. If the value of 533 | the key-value pair on the first level is another dictionary, the key is 534 | considered to be the name of the section and the inner dictionary as properties 535 | of the section. 536 | 537 | The above variable can be used in the template file like this: 538 | 539 | .. code:: jinja2 540 | 541 | {{ my_rsyncd_config | encode_ini }} 542 | 543 | The output of such template would be: 544 | 545 | .. code:: ini 546 | 547 | gid=nobody 548 | max connections=4 549 | pid file=/run/rsyncd.pid 550 | syslog facility=local5 551 | uid=nobody 552 | use chroot=False 553 | 554 | [ftp] 555 | comment=ftp area 556 | path=/srv/ftp 557 | 558 | The special value :code:`!!!null` can be used, to create a key without any value. 559 | This must be wrapped in quotes, to avoid being evaluated as a YAML tag. 560 | 561 | Take the following data structure: 562 | 563 | .. code:: yaml 564 | 565 | config: 566 | myconf: 567 | key: value 568 | keyWithoutValue: '!!!null' 569 | 570 | This would become: 571 | 572 | .. code:: ini 573 | 574 | [myconf] 575 | key=value 576 | keyWithoutValue 577 | 578 | The filter can have the following parameters: 579 | 580 | - ``comment="#"`` 581 | 582 | Sign used to comment out lines when `section_is_comment=true`. 583 | 584 | - ``delimiter="="`` 585 | 586 | Sign separating the *property* and the *value*. By default it's set to 587 | ``'='`` but it can also be set for example to ``' = '``. 588 | 589 | - ``indent=""`` 590 | 591 | Indent the keys with the specified string. E.g. ``indent="\t"``. 592 | 593 | - ``quote=""`` 594 | 595 | Sets the quoting of the value. Use ``quote="'"`` or ``quote='"'``. 596 | 597 | - ``section_is_comment=false`` 598 | 599 | If this parameter is set to ``true``, the section value will be used as 600 | a comment for the following properties of the section. 601 | 602 | - ``ucase_prop=false`` 603 | 604 | Indicates whether the *property* should be made upper case. 605 | 606 | 607 | .. _encode-json: 608 | 609 | encode_json 610 | ^^^^^^^^^^^ 611 | 612 | This filter helps to create configuration in the JSON format. The 613 | expected data structure is the following: 614 | 615 | .. code:: yaml 616 | 617 | my_sensu_client_config: 618 | client: 619 | name: localhost 620 | address: 127.0.0.1 621 | subscriptions: 622 | - test 623 | 624 | Because JSON is very similar to YAML, the variable consists of 625 | dictionaries of which value can be either an simple type (number, string, 626 | boolean), list or another dictionary. All can be nested in any number of 627 | levels. 628 | 629 | The above variable can be used in the template file like this: 630 | 631 | .. code:: jinja2 632 | 633 | {{ my_sensu_client_config | encode_json }} 634 | 635 | The output of such template would be: 636 | 637 | .. code:: json 638 | 639 | { 640 | "client": { 641 | "address": "127.0.0.1", 642 | "name": "localhost", 643 | "subscriptions": [ 644 | "test" 645 | ] 646 | } 647 | } 648 | 649 | The filter can have the following parameters: 650 | 651 | - ``convert_bools=false`` 652 | 653 | Indicates whether Boolean values presented as a string should be 654 | converted to a real Boolean value. For example ``var1: 'True'`` would 655 | be represented as a string but by using the ``convert_bools=true`` it 656 | will be converted into Boolean like it would be defined like ``var1: 657 | true``. 658 | 659 | - ``convert_nums=false`` 660 | 661 | Indicates whether number presented as a string should be converted to 662 | number. For example ``var1: '123'`` would be represented as a string 663 | but by using the ``convert_nums=true`` it will be converted it to a 664 | number like it would be defined like ``var1: 123``. It's also possible 665 | to use the YAML type casting to convert string to number (e.g. ``!!int 666 | "1234"``, ``!!float "3.14"``). 667 | 668 | - ``indent=" "`` 669 | 670 | Defines the indentation unit. 671 | 672 | - ``level=0`` 673 | 674 | Indicates the initial level of the indentation. Value ``0`` starts 675 | indenting from the beginning of the line. Setting the value to higher 676 | than ``0`` indents the content by ``indent * level``. 677 | 678 | 679 | .. _encode-logstash: 680 | 681 | encode_logstash 682 | ^^^^^^^^^^^^^^^ 683 | 684 | This filter helps to create configuration in the format used by Logstash. 685 | The expected data structure is the following: 686 | 687 | .. code:: yaml 688 | 689 | my_logstash_config: 690 | - :input: 691 | - :file: 692 | path: /var/log/httpd/access_log 693 | start_position: beginning 694 | - :filter: 695 | - ':if [path] =~ "access"': 696 | - :mutate: 697 | replace: 698 | type: apache_access 699 | - :grok: 700 | match: 701 | message: "%{COMBINEDAPACHELOG}" 702 | - :date: 703 | match: 704 | - timestamp 705 | - dd/MMM/yyyy:HH:mm:ss Z 706 | - ':else if [path] =~ "error"': 707 | - :mutate: 708 | replace: 709 | type: "apache_error" 710 | - :else: 711 | - :mutate: 712 | replace: 713 | type: "random_logs" 714 | - :output: 715 | - :elasticsearch: 716 | hosts: 717 | - localhost:9200 718 | - :stdout: 719 | codec: rubydebug 720 | 721 | The variable consists of a list of sections where each section is 722 | prefixed by a special character specified by the ``section_prefix`` 723 | (``:`` by default). The value of the top level sections can be either 724 | another section or a dictionary. The value of the dictionary can be a 725 | simple value, list or another dictionary. 726 | 727 | The above variable can be used in the template file like this: 728 | 729 | .. code:: jinja2 730 | 731 | {{ my_logstash_config | encode_logstash }} 732 | 733 | The output of such template would be: 734 | 735 | .. code:: logstash 736 | 737 | input { 738 | file { 739 | path => "/var/log/httpd/access_log" 740 | start_position => "beginning" 741 | } 742 | } 743 | filter { 744 | if [path] =~ "access" { 745 | mutate { 746 | replace => { 747 | "type" => "apache_access" 748 | } 749 | } 750 | grok { 751 | match => { 752 | "message" => "%{COMBINEDAPACHELOG}" 753 | } 754 | } 755 | date { 756 | match => [ 757 | "timestamp", 758 | "dd/MMM/yyyy:HH:mm:ss Z" 759 | ] 760 | } 761 | } 762 | else if [path] =~ "error" { 763 | mutate { 764 | replace => { 765 | "type" => "apache_error" 766 | } 767 | } 768 | } 769 | else { 770 | mutate { 771 | replace => { 772 | "type" => "random_logs" 773 | } 774 | } 775 | } 776 | } 777 | output { 778 | elasticsearch { 779 | hosts => [ 780 | "localhost:9200" 781 | ] 782 | } 783 | stdout { 784 | codec => "rubydebug" 785 | } 786 | } 787 | 788 | The filter can have the following parameters: 789 | 790 | - ``backslash_ignore_prefix='@@@'`` 791 | 792 | This parameter defines a sets of characters than can be prepended to a string 793 | to prevent backslahes from being escaped in the resulting configuration (e.g. 794 | ``"@@@sshd(?:\[%{POSINT:[system][auth][pid]}\])?:"`` will turn to 795 | ``"sshd(?:\[%{POSINT:[system][auth][pid]}\])?:"`` instead of to 796 | ``"sshd(?:\\[%{POSINT:[system][auth][pid]}\\])?:"``). 797 | 798 | - ``convert_bools=false`` 799 | 800 | Indicates whether Boolean values presented as a string should be 801 | converted to a real Boolean value. For example ``var1: 'True'`` would 802 | be represented as a string but by using the ``convert_bools=true`` it 803 | will be converted into Boolean like it would be defined like ``var1: 804 | true``. 805 | 806 | - ``convert_nums=false`` 807 | 808 | Indicates whether number presented as a string should be converted to 809 | number. For example ``var1: '123'`` would be represented as a string 810 | but by using the ``convert_nums=true`` it will be converted it to a 811 | number like it would be defined like ``var1: 123``. It's also possible 812 | to use the YAML type casting to convert string to number (e.g. ``!!int 813 | "1234"``, ``!!float "3.14"``). 814 | 815 | - ``indent=" "`` 816 | 817 | Defines the indentation unit. 818 | 819 | - ``level=0`` 820 | 821 | Indicates the initial level of the indentation. Value ``0`` starts 822 | indenting from the beginning of the line. Setting the value to higher 823 | than ``0`` indents the content by ``indent * level``. 824 | 825 | - ``section_prefix=":"`` 826 | 827 | This parameter specifies which character will be used to identify the 828 | Logstash section. 829 | 830 | 831 | .. _encode-lua: 832 | 833 | encode_lua 834 | ^^^^^^^^^^ 835 | 836 | This filter helps to create configuration in a Lua friendly format. 837 | The expected data structure is the following: 838 | 839 | .. code:: yaml 840 | 841 | my_lua_config: 842 | fork: false 843 | external_addresses: 844 | - 1.2.3.4 845 | - 5.6.7.8 846 | admins: 847 | - admin@example.com 848 | contact_info: 849 | abuse: abuse@example.com 850 | admin: admin@example.com 851 | 852 | Lua is a small scripting language, often embedded into C/C++ applications. 853 | This encoder does a best effort to match configuration files seen in the wild, 854 | while allowing the user to further customize how the final output is rendered. 855 | 856 | The above variable can be used in the template file like this: 857 | 858 | .. code:: jinja2 859 | 860 | {{ my_lua_config | encode_lua }} 861 | 862 | The output of such a template would be: 863 | 864 | .. code:: lua 865 | 866 | fork = false; 867 | external_addresses = { 868 | "1.2.3.4"; 869 | "5.6.7.8"; 870 | } 871 | admins = { 872 | "admin@example.com"; 873 | } 874 | contact_info = { 875 | abuse = "abuse@example.com"; 876 | admin = "admin@example.com"; 877 | } 878 | 879 | The filter can have the following parameters: 880 | 881 | - ``convert_bools=false`` 882 | 883 | Indicates whether Boolean values presented as a string should be 884 | converted to a real Boolean value. For example ``var1: 'True'`` would 885 | be represented as a string but by using the ``convert_bools=true`` it 886 | will be converted into Boolean like it would be defined like ``var1: 887 | true``. 888 | 889 | - ``convert_nums=false`` 890 | 891 | Indicates whether number presented as a string should be converted to 892 | number. For example ``var1: '123'`` would be represented as a string 893 | but by using the ``convert_nums=true`` it will be converted it to a 894 | number like it would be defined like ``var1: 123``. It's also possible 895 | to use the YAML type casting to convert string to number (e.g. ``!!int 896 | "1234"``, ``!!float "3.14"``). 897 | 898 | - ``sort_keys=false`` 899 | 900 | Indicates whether the keys should be sorted when the output is rendered, 901 | or left to python's implicit handling of dict ordering. 902 | 903 | - ``indent=" "`` 904 | 905 | Defines the indentation unit. 906 | 907 | - ``level=0`` 908 | 909 | Indicates the initial level of the indentation. Value ``0`` starts 910 | indenting from the beginning of the line. Setting the value to higher 911 | than ``0`` indents the content by ``indent * level``. 912 | 913 | 914 | .. _encode-nginx: 915 | 916 | encode_nginx 917 | ^^^^^^^^^^^^ 918 | 919 | This filter helps to create configuration in the format used by Nginx 920 | web server. The expected data structure is the following: 921 | 922 | .. code:: yaml 923 | 924 | my_nginx_vhost_config: 925 | - server: 926 | - listen 80 927 | - server_name $hostname 928 | - "location /": 929 | - root /srv/www/myapp 930 | - index index.html 931 | 932 | As Nginx configuration is order sensitive, the all configuration is 933 | defined as a nested list. As it would be difficult to recognize how many 934 | elements each configuration definition has, the list item value is no 935 | further separated into key/value dictionary. Every line of the 936 | configuration is treated either as a key indicating another nested list 937 | or simply as a string. 938 | 939 | The above variable can be used in the template file like this: 940 | 941 | .. code:: jinja2 942 | 943 | {{ my_nginx_vhost | encode_nginx }} 944 | 945 | The output of such template would be: 946 | 947 | .. code:: nginx 948 | 949 | server { 950 | listen 80; 951 | server_name $hostname; 952 | 953 | location / { 954 | root /srv/www/myapp; 955 | index index.html; 956 | } 957 | } 958 | 959 | The filter can have the following parameters: 960 | 961 | - ``block_semicolon=false`` 962 | 963 | Allows to add a semicolon to the end of each block. 964 | 965 | - ``indent=" "`` 966 | 967 | Defines the indentation unit. 968 | 969 | - ``level=0`` 970 | 971 | Indicates the initial level of the indentation. Value ``0`` starts 972 | indenting from the beginning of the line. Setting the value to higher 973 | than ``0`` indents the content by ``indent * level``. 974 | 975 | - ``semicolon=';'`` 976 | 977 | Semicolon character. Set this to empty string to ignore all semicolons. 978 | 979 | - ``semicolon_ignore_postfix='!;'`` 980 | 981 | If the line ends with ``!;`` then don't add the final semicolon. 982 | 983 | 984 | .. _encode-pam: 985 | 986 | encode_pam 987 | ^^^^^^^^^^ 988 | 989 | This filter helps to create configuration in the format user by Linux 990 | Pluggable Authentication Modules (PAM). The expected data structure is 991 | the following: 992 | 993 | .. code:: yaml 994 | 995 | my_system_auth_config: 996 | aa: 997 | type: auth 998 | control: required 999 | path: pam_unix.so 1000 | args: 1001 | - try_first_pass 1002 | - nullok 1003 | bb: 1004 | type: auth 1005 | control: optional 1006 | path: pam_permit.so 1007 | cc: 1008 | type: auth 1009 | control: required 1010 | path: pam_env.so 1011 | dd: 1012 | type: account 1013 | control: required 1014 | path: pam_unix.so 1015 | ee: 1016 | type: account 1017 | control: optional 1018 | path: pam_permit.so 1019 | ff: 1020 | type: account 1021 | control: required 1022 | path: pam_time.so 1023 | gg: 1024 | type: password 1025 | control: required 1026 | path: pam_unix.so 1027 | args: 1028 | - try_first_pass 1029 | - nullok 1030 | - sha512 1031 | - shadow 1032 | hh: 1033 | type: password 1034 | control: optional 1035 | path: pam_permit.so 1036 | args: 1037 | ii: 1038 | type: session 1039 | control: required 1040 | path: pam_limits.so 1041 | jj: 1042 | type: session 1043 | control: required 1044 | path: pam_unix.so 1045 | kk: 1046 | type: session 1047 | control: optional 1048 | path: pam_permit.so 1049 | 1050 | The variable is a dictionary of which the key is a labels and the value 1051 | is the PAM rule. The label is used to order the PAM rules. Using labels 1052 | with even number of characters allows to insert another rule in between 1053 | of any two rules. 1054 | 1055 | The above variable can be used in the template file like this: 1056 | 1057 | .. code:: jinja2 1058 | 1059 | {{ my_system_auth_config | encode_pam }} 1060 | 1061 | The output of such template would be: 1062 | 1063 | .. code:: pam 1064 | 1065 | auth required pam_unix.so try_first_pass nullok 1066 | auth optional pam_permit.so 1067 | auth required pam_env.so 1068 | 1069 | account required pam_unix.so 1070 | account optional pam_permit.so 1071 | account required pam_time.so 1072 | 1073 | password required pam_unix.so try_first_pass nullok sha512 shadow 1074 | password optional pam_permit.so 1075 | 1076 | session required pam_limits.so 1077 | session required pam_unix.so 1078 | session optional pam_permit.so 1079 | 1080 | The filter can have the following parameters: 1081 | 1082 | - ``print_label=false`` 1083 | 1084 | Print labels as a comment in the output. 1085 | 1086 | - ``separate_types=true`` 1087 | 1088 | Add a newline between the groups of types. 1089 | 1090 | - ``separator=" "`` 1091 | 1092 | Separator between the collection of tokens. 1093 | 1094 | 1095 | .. _encode-toml: 1096 | 1097 | encode_toml 1098 | ^^^^^^^^^^^ 1099 | 1100 | This filter helps to create configuration in the TOML format. The 1101 | expected data structure is the following: 1102 | 1103 | .. code:: yaml 1104 | 1105 | my_grafana_ldap_config: 1106 | verbose_logging: false 1107 | servers: 1108 | - host: 127.0.0.1 1109 | port: 389 1110 | use_ssl: no 1111 | start_tls: no 1112 | ssl_skip_verify: no 1113 | bind_dn: cn=admin,dc=grafana,dc=org 1114 | bind_password: grafana 1115 | search_filter: (cn=%s) 1116 | search_base_dns: 1117 | - dc=grafana,dc=org 1118 | - attributes: 1119 | name: givenName 1120 | surname: sn 1121 | username: cn 1122 | member_of: memberOf 1123 | email: email 1124 | - group_mappings: 1125 | - group_dn: cn=admins,dc=grafana,dc=org 1126 | org_role: Admin 1127 | - group_dn: cn=users,dc=grafana,dc=org 1128 | org_role: Editor 1129 | - group_dn: "*" 1130 | org_role: Viewer 1131 | 1132 | The variable is a dictionary of which value can be either a simple type 1133 | (number, string, boolean), list or another dictionary. The dictionaries 1134 | and lists can be nested. 1135 | 1136 | The above variable can be used in the template file like this: 1137 | 1138 | .. code:: jinja2 1139 | 1140 | {{ my_grafana_ldap_config | encode_toml }} 1141 | 1142 | The output of such template would be: 1143 | 1144 | .. code:: toml 1145 | 1146 | verbose_logging = false 1147 | 1148 | [[servers]] 1149 | host = "127.0.0.1" 1150 | port = 389 1151 | use_ssl = false 1152 | start_tls = false 1153 | ssl_skip_verify = false 1154 | bind_dn = "cn=admin,dc=grafana,dc=org" 1155 | bind_password = 'grafana' 1156 | search_filter = "(cn=%s)" 1157 | search_base_dns = ["dc=grafana,dc=org"] 1158 | 1159 | [servers.attributes] 1160 | name = "givenName" 1161 | surname = "sn" 1162 | username = "cn" 1163 | member_of = "memberOf" 1164 | email = "email" 1165 | 1166 | [[servers.group_mappings]] 1167 | group_dn = "cn=admins,dc=grafana,dc=org" 1168 | org_role = "Admin" 1169 | 1170 | [[servers.group_mappings]] 1171 | group_dn = "cn=users,dc=grafana,dc=org" 1172 | org_role = "Editor" 1173 | 1174 | [[servers.group_mappings]] 1175 | group_dn = "*" 1176 | org_role = "Viewer" 1177 | 1178 | The filter can have the following parameters: 1179 | 1180 | - ``convert_bools=false`` 1181 | 1182 | Indicates whether Boolean values presented as a string should be 1183 | converted to a real Boolean value. For example ``var1: 'True'`` would 1184 | be represented as a string but by using the ``convert_bools=true`` it 1185 | will be converted into Boolean like it would be defined like ``var1: 1186 | true``. 1187 | 1188 | - ``convert_nums=false`` 1189 | 1190 | Indicates whether number presented as a string should be converted to 1191 | number. For example ``var1: '123'`` would be represented as a string 1192 | but by using the ``convert_nums=true`` it will be converted it to a 1193 | number like it would be defined like ``var1: 123``. It's also possible 1194 | to use the YAML type casting to convert string to number (e.g. ``!!int 1195 | "1234"``, ``!!float "3.14"``). 1196 | 1197 | - ``quote='"'`` 1198 | 1199 | Sets the quoting of the value. Use ``quote="'"`` or ``quote='"'``. 1200 | 1201 | 1202 | .. _encode-xml: 1203 | 1204 | encode_xml 1205 | ^^^^^^^^^^ 1206 | 1207 | This filter helps to create configuration in the XML format. The expected 1208 | data structure is the following: 1209 | 1210 | .. code:: yaml 1211 | 1212 | my_oddjob_config: 1213 | - oddjobconfig: 1214 | - service: 1215 | - ^name: com.redhat.oddjob 1216 | - object: 1217 | - ^name: /com/redhat/oddjob 1218 | - interface: 1219 | - ^name: com.redhat.oddjob 1220 | - method: 1221 | - ^name: listall 1222 | - allow: 1223 | - ^min_uid: 0 1224 | - ^max_uid: 0 1225 | - method: 1226 | - ^name: list 1227 | - allow: '' 1228 | - method: 1229 | - ^name: quit 1230 | - allow: 1231 | - ^user: root 1232 | - method: 1233 | - ^name: reload 1234 | - allow: 1235 | - ^user: root 1236 | - include: 1237 | - ^ignore_missing: "yes" 1238 | - /etc/oddjobd.conf.d/*.conf 1239 | - include: 1240 | - ^ignore_missing: "yes" 1241 | - /etc/oddjobd-local.conf 1242 | 1243 | The variable can be a list of dictionaries, lists or strings. This config 1244 | encoder does not handle mixed content very well so the safest way how to 1245 | include mixed content is to define it as a string and use the parameter 1246 | ``escape_xml=false``. This config encoder also produces no XML declaration. 1247 | Any XML declaration or DOCTYPE must be a part of the template file. 1248 | 1249 | The above variable can be used in the template file like this: 1250 | 1251 | .. code:: jinja2 1252 | 1253 | {{ my_oddjob_config | encode_xml }} 1254 | 1255 | The output of such template would be: 1256 | 1257 | .. code:: xml 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 | 1278 | /etc/oddjobd.conf.d/*.conf 1279 | /etc/oddjobd-local.conf 1280 | 1281 | 1282 | The filter can have the following parameters: 1283 | 1284 | - ``attribute_sign="^"`` 1285 | 1286 | XML attribute indicator. 1287 | 1288 | - ``indent=" "`` 1289 | 1290 | Defines the indentation unit. 1291 | 1292 | - ``level=0`` 1293 | 1294 | Indicates the initial level of the indentation. Value ``0`` starts 1295 | indenting from the beginning of the line. Setting the value to higher 1296 | than ``0`` indents the content by ``indent * level``. 1297 | 1298 | 1299 | .. _encode-yaml: 1300 | 1301 | encode_yaml 1302 | ^^^^^^^^^^^ 1303 | 1304 | This filter helps to create configuration in the YAML format. The 1305 | expected data structure is the following: 1306 | 1307 | .. code:: yaml 1308 | 1309 | my_mongodb_config: 1310 | systemLog: 1311 | destination: file 1312 | logAppend: true 1313 | path: /var/log/mongodb/mongod.log 1314 | storage: 1315 | dbPath: /var/lib/mongo 1316 | journal: 1317 | enabled: true 1318 | processManagement: 1319 | fork: true 1320 | pidFilePath: /var/run/mongodb/mongod.pid 1321 | net: 1322 | port: 27017 1323 | bindIp: 127.0.0.1 1324 | 1325 | The variable is ordinary YAML. The only purpose of this encoder filter is 1326 | to be able to convert YAML data structure into the string in a template 1327 | file in unified way compatible with the other config encoders. 1328 | 1329 | The above variable can be used in the template file like this: 1330 | 1331 | .. code:: jinja2 1332 | 1333 | {{ my_mongodb_config | encode_yaml }} 1334 | 1335 | The output of such template would be: 1336 | 1337 | .. code:: yaml 1338 | 1339 | net: 1340 | bindIp: "127.0.0.1" 1341 | port: 27017 1342 | processManagement: 1343 | fork: true 1344 | pidFilePath: "/var/run/mongodb/mongod.pid" 1345 | storage: 1346 | dbPath: "/var/lib/mongo" 1347 | journal: 1348 | enabled: true 1349 | systemLog: 1350 | destination: "file" 1351 | logAppend: true 1352 | path: "/var/log/mongodb/mongod.log" 1353 | 1354 | The filter can have the following parameters: 1355 | 1356 | - ``block_prefix=';;;'`` 1357 | 1358 | Allows to maintain block signs in the output. 1359 | 1360 | .. code:: yaml 1361 | 1362 | aaa: |- 1363 | ;;;|- 1364 | bbb = ccc 1365 | ddd = eee 1366 | 1367 | - ``convert_bools=false`` 1368 | 1369 | Indicates whether Boolean values presented as a string should be 1370 | converted to a real Boolean value. For example ``var1: 'True'`` would 1371 | be represented as a string but by using the ``convert_bools=true`` it 1372 | will be converted into Boolean like it would be defined like ``var1: 1373 | true``. 1374 | 1375 | - ``convert_nums=false`` 1376 | 1377 | Indicates whether number presented as a string should be converted to 1378 | number. For example ``var1: '123'`` would be represented as a string 1379 | but by using the ``convert_nums=true`` it will be converted it to a 1380 | number like it would be defined like ``var1: 123``. It's also possible 1381 | to use the YAML type casting to convert string to number (e.g. ``!!int 1382 | "1234"``, ``!!float "3.14"``). 1383 | 1384 | - ``indent=" "`` 1385 | 1386 | Defines the indentation unit. 1387 | 1388 | - ``level=0`` 1389 | 1390 | Indicates the initial level of the indentation. Value ``0`` starts 1391 | indenting from the beginning of the line. Setting the value to higher 1392 | than ``0`` indents the content by ``indent * level``. 1393 | 1394 | - ``quote='"'`` 1395 | 1396 | Sets the quoting of the value. Use ``quote="'"`` or ``quote='"'``. 1397 | 1398 | 1399 | .. _Utilities: 1400 | 1401 | Utilities 1402 | --------- 1403 | 1404 | The followng is a list of utilities that can be used in conjunction with the 1405 | Config Encoder filters. 1406 | 1407 | 1408 | .. _template-replace: 1409 | 1410 | template_replace 1411 | ^^^^^^^^^^^^^^^^ 1412 | 1413 | This filter allows to use extra templating layer which gets processed during 1414 | the template file processing. That can be useful if it's necessary to create 1415 | repetitive but slightly different definitions inside the template file. 1416 | 1417 | The extra templating layer is represented by a templating variable which 1418 | contains specially decorated variables which get replaced by its real value at 1419 | the time of template file processing. The template variable can be composed 1420 | dynamically which provides extra flexibility that would otherwise have to be 1421 | hardcoded in the template file. 1422 | 1423 | The filter expects the template variable containing the specially decorated 1424 | variables as its input. The filter has one parameter which is used to replaced 1425 | the specially decorated variables in the template variable. 1426 | 1427 | Let's have a look at an example of such usage: 1428 | 1429 | .. code:: yaml 1430 | 1431 | # The variable used as the replacement in the template variable 1432 | my_clients: 1433 | - host: myclient01 1434 | jobdefs: Default 1435 | password: Passw0rd1 1436 | file_retention: 30 days 1437 | - host: myclient02 1438 | jobdefs: HomeOnly 1439 | password: Passw0rd2 1440 | file_retention: 90 days 1441 | 1442 | # The actual template variable used in the template file 1443 | bacula_director_config_job_client: 1444 | # First template variable containing the specially decorated variables 1445 | - template: 1446 | - Job: 1447 | - Name = Job-{[{ item['jobdefs'] }]}-{[{ item['host'] }]} 1448 | - Client = {[{ item['host'] }]}-fd 1449 | - JobDefs = {[{ item['jobdefs'] }]} 1450 | # Variable used to replace the specially decorated variables 1451 | items: "{{ my_clients }}" 1452 | # Second template and its items 1453 | - template: 1454 | - Client: 1455 | - Name = {[{ item['host'] }]}-fd 1456 | - Address = {[{ item['host'] }]} 1457 | - FD Port = 9102 1458 | - Catalog = Default 1459 | - Password = {[{ item['password'] }]} 1460 | - File Retention = {[{ item['file_retention'] }]} 1461 | - Job Retention = 3 months 1462 | - AutoPrune = yes 1463 | items: "{{ my_clients }}" 1464 | 1465 | The above variable can be used together with the `template_replace` filter in 1466 | the template file (``bacula-dir.conf.j2``) like this: 1467 | 1468 | .. code:: jinja2 1469 | 1470 | {% for record in bacula_director_config_job_client %} 1471 | {%- for item in record['items'] -%} 1472 | {{ record['template'] | template_replace(item) | encode_nginx }}{{ "\n" }} 1473 | {%- endfor -%} 1474 | {% endfor %} 1475 | 1476 | The template file can be called from the playbook/role like this: 1477 | 1478 | .. code:: yaml 1479 | 1480 | - name: Configure Bacula Director 1481 | template: 1482 | src: bacula-dir.conf.j2 1483 | dest: /etc/bacula/bacula-dir.conf 1484 | 1485 | And the result of such usage is the following: 1486 | 1487 | .. code:: nginx 1488 | 1489 | Job { 1490 | Name = Job-Default-myclient01; 1491 | Client = myclient01-fd; 1492 | JobDefs = Default; 1493 | } 1494 | 1495 | Job { 1496 | Name = Job-HomeOnly-myclient02; 1497 | Client = myclient02-fd; 1498 | JobDefs = HomeOnly; 1499 | } 1500 | 1501 | Client { 1502 | Name = myclient01-fd; 1503 | Address = myclient01; 1504 | FD Port = 9102; 1505 | Catalog = Default; 1506 | Password = Passw0rd1; 1507 | File Retention = 30 days; 1508 | Job Retention = 3 months; 1509 | AutoPrune = yes; 1510 | } 1511 | 1512 | Client { 1513 | Name = myclient02-fd; 1514 | Address = myclient02; 1515 | FD Port = 9102; 1516 | Catalog = Default; 1517 | Password = Passw0rd2; 1518 | File Retention = 90 days; 1519 | Job Retention = 3 months; 1520 | AutoPrune = yes; 1521 | } 1522 | 1523 | 1524 | .. _Testing: 1525 | 1526 | Testing 1527 | ------- 1528 | 1529 | All encoders have a set of unit tests automated through 1530 | `tox `_. Full test can be executed like this: 1531 | 1532 | .. code:: shell 1533 | 1534 | $ tox 1535 | 1536 | Individual encoder can be tested like this: 1537 | 1538 | .. code:: shell 1539 | 1540 | $ tox -- python -m unittest tests.test_config_encoders.TestYaml 1541 | 1542 | Individual tests can be executed like this: 1543 | 1544 | .. code:: shell 1545 | 1546 | $ tox -- python -m unittest tests.test_config_encoders.TestYaml.test_string 1547 | 1548 | Tests are great source of advanced examples of how to use each of the encoders. 1549 | Explore them in the directory ``tests/files``. The content of the ``.in`` files 1550 | must be assigned to a variable when using in Ansible. The output in the 1551 | ``.out`` files might depend on additional parameters used in the 1552 | ``tests/test_config_encoders.py`` file. Testing via Ansible can be executed 1553 | like this: 1554 | 1555 | .. code:: shell 1556 | 1557 | $ ansible-playbook -i localhost, tests/test_config_encoders.yaml 1558 | 1559 | 1560 | .. _License: 1561 | 1562 | License 1563 | ------- 1564 | 1565 | GPLv3 1566 | 1567 | 1568 | .. _Author: 1569 | 1570 | Author 1571 | ------ 1572 | 1573 | Jiri Tyr 1574 | --------------------------------------------------------------------------------