diff --git a/keepalived/templates/config.jinja b/keepalived/templates/config.jinja new file mode 100644 index 0000000..8219581 --- /dev/null +++ b/keepalived/templates/config.jinja @@ -0,0 +1,42 @@ +{%- set key = 0 -%} +{%- set value = 1 -%} +{%- set carryovers = ['real_server', 'virtual_server', 'virtual_server_group', + 'vrrp_instance', 'vrrp_script', 'vrrp_sync_group'] -%} +{%- macro keepalived_config(data, carryover='', recurse=-1, indent=0) -%} + {%- set recurse = recurse + 1 -%} + {%- if data is none -%} + {{- '\n' -}} + {%- elif data is string or data is number -%} + {{- data|string|indent(indent, True) }}{{ '\n' -}} + {%- else -%} + {%- if recurse > 0 -%} + {{- '{\n' -}} + {%- set indent = indent + 2 -%} + {%- endif -%} + {%- if data is mapping -%} + {%- for item in data|dictsort -%} + {%- if item[key] in carryovers -%} + {{- keepalived_config(item[value], carryover=item[key], indent=indent) -}} + {%- else -%} + {%- set carryIndent = indent -%} + {%- set forwardIndent = indent -%} + {%- if carryover -%} + {{- carryover|indent(indent, True) }}{{ ' ' -}} + {%- set carryIndent = 0 -%} + {%- endif -%} + {%- if item[value] is string or item[value] is not iterable -%} + {%- set forwardIndent = 0 -%} + {%- endif -%} + {{- item[key]|indent(carryIndent, True) }} {{ keepalived_config(item[value], recurse=recurse, indent=forwardIndent) -}} + {%- endif -%} + {%- endfor -%} + {%- else -%} + {%- for item in data -%} + {{- keepalived_config(item, indent=indent) -}} + {%- endfor -%} + {%- endif -%} + {%- if recurse > 0 -%} + {{- '}'|indent(indent - 2, True) -}}{{ '\n' }} + {%- endif -%} + {%- endif -%} +{%- endmacro -%} diff --git a/keepalived/templates/keepalived.jinja b/keepalived/templates/keepalived.jinja index 3aaf5e1..b6fa7eb 100644 --- a/keepalived/templates/keepalived.jinja +++ b/keepalived/templates/keepalived.jinja @@ -6,58 +6,10 @@ # Any changes will be overwritten. {{ '\n' }} -{# -Macro Explanation: - -This is a recursive macro that takes the type of entry and determines how -it is suppose to appear in the configuration file. Strings and numbers -are just written out. Lists and hashes are placed inside if couple of -parenthesis. It also takes into account the special groupings like -vrrp_instance and virtual_server. Any additional data from a list or a -hash is then processed by calling the macro again. - -Forced carriage returns and use of jinja's indent are only there to make -the final file more human readable. They serve no other function. -#} - +{%- import 'keepalived/templates/config.jinja' as config -%} {%- import_yaml 'keepalived/defaults.yaml' as keepalived_defaults -%} {%- set keepalived_final_values = salt.pillar.get( 'keepalived', default=keepalived_defaults, merge=True) -%} - -{%- set groupings = ['vrrp_script', 'vrrp_sync_group', 'vrrp_instance', - 'virtual_server_group', 'virtual_server', 'real_server'] -%} -{%- macro config_entries(data, indents, carryover='') -%} - {%- if data is string or data is number -%} - {{- data|string|indent(indents, True) }}{{ '\n' -}} - {%- elif data is none -%} - {{- '\n' -}} - {%- else -%} - {%- if indents != 0 and not carryover -%} - {{- " {\n" -}} - {%- endif -%} - {%- if data is mapping -%} - {%- for entry in data|dictsort -%} - {%- if entry[0] in groupings -%} - {{- config_entries(entry[1], indents, carryover=entry[0]) -}} - {%- else -%} - {%- if carryover -%} - {{- carryover|indent(indents, True) }}{{ ' ' }} - {%- endif -%} - {{- entry[0]|indent(indents, True) }} - {{- config_entries(entry[1], indents + 2) -}} - {%- endif -%} - {%- endfor -%} - {%- else -%} - {%- for entry in data -%} - {{- config_entries(entry, indents) -}} - {%- endfor -%} - {%- endif -%} - {%- if indents != 0 and not carryover -%} - {{- '}'|indent(indents - 2, True) }}{{ '\n' }} - {%- endif -%} - {%- endif -%} -{%- endmacro -%} - -{{ config_entries(keepalived_final_values, 0) }} +{{ config.keepalived_config(keepalived_final_values) }} diff --git a/keepalived/templates/test_config.jinja b/keepalived/templates/test_config.jinja new file mode 100644 index 0000000..baf56b5 --- /dev/null +++ b/keepalived/templates/test_config.jinja @@ -0,0 +1,2 @@ +{% import 'config.jinja' as config %} +{{ config.keepalived_config(testdata) }} diff --git a/pillar.example b/pillar.example index 99b8db7..43fdd54 100644 --- a/pillar.example +++ b/pillar.example @@ -1,6 +1,15 @@ # # Example pillar configuration # +# Boolean entries must be placed stored as strings, otherwise it will show +# up as 1 or 0 in the config file. +# +# Anything that needs to be in quotes in the configuration file needs to +# be escaped in the yaml file. Otherwise the quotes will not appear in +# the config file. +# +# When order is important, put the entries into a yaml array or list. This +# could be used to place vrrp_script before vrrp_instance entries. # The following would generate the example file in RedHat based systems. diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_keepalived_config.py b/test/test_keepalived_config.py new file mode 100755 index 0000000..c275160 --- /dev/null +++ b/test/test_keepalived_config.py @@ -0,0 +1,118 @@ +#!/usr/bin/python + +import os, unittest +from jinja2 import Environment, FileSystemLoader + +class TestKeepalivedConfiguration(unittest.TestCase): + + def setUp(self): + self.t_dir = os.path.abspath(os.path.join( + os.path.dirname(__file__), + os.pardir, + 'keepalived', + 'templates')) + self.t_conf = Environment(loader=FileSystemLoader(self.t_dir), + trim_blocks=True) + + def renderTest(self, data, result): + holder = self.t_conf.get_template('test_config.jinja').render(testdata=data) + output = repr(holder) + ' did not equal ' + repr(result) + self.assertEqual(holder, result, output) + + def test_string(self): + testdata = 'stuff' + result = 'stuff\n' + self.renderTest(testdata, result) + + def test_number(self): + testdata = 3 + result = '3\n' + self.renderTest(testdata, result) + + def test_null(self): + testdata = None + result = '\n' + self.renderTest(testdata, result) + + def test_key_value_pair(self): + testdata = {'flintstone': 'fred'} + result = 'flintstone fred\n' + self.renderTest(testdata, result) + + def test_key_array_pair(self): + testdata = {'flintstone': ['fred', 'wilma', 'pebbles']} + result = 'flintstone {\n fred\n wilma\n pebbles\n}\n' + self.renderTest(testdata, result) + + def test_key_hash_pair(self): + testdata = {'friends': {'rubble': 'barney'}} + result = 'friends {\n rubble barney\n}\n' + self.renderTest(testdata, result) + + def test_key_ordered_hashs(self): + testdata = {'friends': {'fred': 'flintstone', 'barney': 'rubble', 'wilma': 'flintstone', 'betty': 'rubble'}} + result = 'friends {\n barney rubble\n betty rubble\n fred flintstone\n wilma flintstone\n}\n' + self.renderTest(testdata, result) + + def test_ordered_hashes(self): + testdata = [{'fred': 'flintstone'}, {'wilma': 'flintstone'}, {'barney': 'rubble'}, {'betty': 'rubble'}] + result = 'fred flintstone\nwilma flintstone\nbarney rubble\nbetty rubble\n' + self.renderTest(testdata, result) + + def test_carryover(self): + testdata = {'vrrp_script': {'gizmo': {'fred': 'flintstone', 'barney': 'rubble'}}} + result = 'vrrp_script gizmo {\n barney rubble\n fred flintstone\n}\n' + self.renderTest(testdata, result) + + def test_carryover_contains_arry(self): + testdata = {'vrrp_script': {'gizmo': [{'fred': 'flintstone'}, {'barney': 'rubble'}]}} + result = 'vrrp_script gizmo {\n fred flintstone\n barney rubble\n}\n' + self.renderTest(testdata, result) + + def test_carryover_vrrp_instance(self): + testdata = {'vrrp_instance': {'gizmo': {'fred': 'flintstone', 'barney': 'rubble'}}} + result = 'vrrp_instance gizmo {\n barney rubble\n fred flintstone\n}\n' + self.renderTest(testdata, result) + + def test_carryovers_in_an_array(self): + testdata = [{'vrrp_script': {'gizmo': {'running': 'dumdums'}}}, {'vrrp_instance': {'dumdums': {'fred': 'flintstone'}}}] + result = 'vrrp_script gizmo {\n running dumdums\n}\nvrrp_instance dumdums {\n fred flintstone\n}\n' + self.renderTest(testdata, result) + + def test_carryover_vrrp_sync_group(self): + testdata = {'vrrp_sync_group': {'gizmo': {'fred': 'flintstone', 'barney': 'rubble'}}} + result = 'vrrp_sync_group gizmo {\n barney rubble\n fred flintstone\n}\n' + self.renderTest(testdata, result) + + def test_carryover_virtual_server_group(self): + testdata = {'virtual_server_group': {'gizmo': {'fred': 'flintstone', 'barney': 'rubble'}}} + result = 'virtual_server_group gizmo {\n barney rubble\n fred flintstone\n}\n' + self.renderTest(testdata, result) + + def test_carryover_virtual_server(self): + testdata = {'virtual_server': {'gizmo': {'fred': 'flintstone', 'barney': 'rubble'}}} + result = 'virtual_server gizmo {\n barney rubble\n fred flintstone\n}\n' + self.renderTest(testdata, result) + + def test_carryover_real_server(self): + testdata = {'real_server': {'gizmo': {'fred': 'flintstone', 'barney': 'rubble'}}} + result = 'real_server gizmo {\n barney rubble\n fred flintstone\n}\n' + self.renderTest(testdata, result) + + def test_carryover_within_carryover(self): + testdata = {'virtual_server': {'gizmo': {'real_server': {'dumdums': {'fred': 'flintstone', 'barney': 'rubble'}}}}} + result = 'virtual_server gizmo {\n real_server dumdums {\n barney rubble\n fred flintstone\n }\n}\n' + self.renderTest(testdata, result) + + def test_indent_nested_hashes(self): + testdata = {'flintstone': {'fred': {'role': {'family': 'father'}}}} + result = 'flintstone {\n fred {\n role {\n family father\n }\n }\n}\n' + self.renderTest(testdata, result) + + def test_indent_carryover_with_nested_hashes(self): + testdata = {'real_server': {'gizmo': {'another': 'day', 'dumdums': {'fred': 'flintstone', 'barney': 'rubble'}}}} + result = 'real_server gizmo {\n another day\n dumdums {\n barney rubble\n fred flintstone\n }\n}\n' + self.renderTest(testdata, result) + +if __name__ == '__main__': + unittest.main()