Merge "Add functionality to generate a base DEA file"
[fuel.git] / deploy / templater.py
1 #!/usr/bin/env python
2 ###############################################################################
3 # Copyright (c) 2016 Ericsson AB and others.
4 # peter.barabas@ericsson.com
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 ###############################################################################
10
11
12 import io
13 import re
14 import yaml
15 from common import(
16     err,
17     ArgParser,
18 )
19
20
21 TAG_START = '%{'
22 TAG_END = '}'
23 DELIMITER = '/'
24
25
26 class Templater(object):
27     def __init__(self, base_file, template_file, output_file):
28         self.template_file = template_file
29         self.output_file = output_file
30         self.base = self.load_yaml(base_file)
31
32     def load_yaml(self, filename):
33         try:
34             with io.open(filename) as yaml_file:
35                 return yaml.load(yaml_file)
36         except Exception as error:
37             err('Error opening YAML file: %s' % error)
38
39     def save_yaml(self, filename, content):
40         try:
41             with io.open(filename, 'w') as yaml_file:
42                 yaml_file.write(content)
43         except Exception as error:
44             err('Error writing YAML file: %s' % error)
45
46     def get_indent(self, line):
47         return len(line) - len(line.lstrip(' '))
48
49     def format_fragment(self, fragment, indent):
50         result = ''
51         is_first_line = True
52
53         for line in fragment.splitlines():
54             # Skip indenting the first line as it is already indented
55             if is_first_line:
56                 line += '\n'
57                 is_first_line = False
58             else:
59                 line = ' ' * indent + line + '\n'
60
61             result += line
62
63         return result.rstrip('\n')
64
65     def format_substitution(self, string):
66         if isinstance(string, basestring):
67             return string
68         else:
69             return yaml.dump(string, default_flow_style=False)
70
71     def parse_interface_tag(self, tag):
72         # Remove 'interface(' prefix, trailing ')' and split arguments
73         args = tag[len('interface('):].rstrip(')').split(',')
74
75         if len(args) == 1 and not args[0]:
76             err('No arguments for interface().')
77         elif len(args) == 2 and (not args[0] or not args[1]):
78             err('Empty argument for interface().')
79         elif len(args) > 2:
80             err('Too many arguments for interface().')
81         else:
82             return args
83
84     def get_interface_from_network(self, interfaces, network):
85         nics = self.base[interfaces]
86         for nic in nics:
87             if network in nics[nic]:
88                 return nic
89
90         err('Network not found: %s' % network)
91
92     def get_role_interfaces(self, role):
93         nodes = self.base['nodes']
94         for node in nodes:
95             if role in node['role']:
96                 return node['interfaces']
97
98         err('Role not found: %s' % role)
99
100     def lookup_interface(self, args):
101         nodes = self.base['nodes']
102
103         if len(args) == 1:
104             interfaces = nodes[0]['interfaces']
105         if len(args) == 2:
106             interfaces = self.get_role_interfaces(args[1])
107
108         return self.get_interface_from_network(interfaces, args[0])
109
110     def parse_include_tag(self, tag):
111         # Remove 'include(' prefix and trailing ')'
112         filename = tag[len('include('):].rstrip(')')
113
114         if not filename:
115             err('No argument for include().')
116
117         return filename
118
119     def include_file(self, filename):
120         fragment = self.load_yaml(filename)
121         return yaml.dump(fragment, default_flow_style=False)
122
123     def parse_tag(self, tag, indent):
124         fragment = ''
125
126         if 'interface(' in tag:
127             args = self.parse_interface_tag(tag)
128             fragment = self.lookup_interface(args)
129         elif 'include(' in tag:
130             filename = self.parse_include_tag(tag)
131             fragment = self.include_file(filename)
132         else:
133             path = tag.split(DELIMITER)
134             fragment = self.base
135             for i in path:
136                 if i in fragment:
137                     fragment = fragment.get(i)
138                 else:
139                     err('Error: key "%s" does not exist in base YAML file' % i)
140
141             fragment = self.format_substitution(fragment)
142
143         return self.format_fragment(fragment, indent)
144
145     def run(self):
146         result = ''
147
148         regex = re.compile(re.escape(TAG_START) + r'([a-z].+)' + re.escape(TAG_END),
149                            flags=re.IGNORECASE)
150         with io.open(self.template_file) as f:
151             for line in f:
152                 indent = self.get_indent(line)
153                 result += re.sub(regex,
154                                  lambda match: self.parse_tag(match.group(1), indent),
155                                  line)
156
157         self.save_yaml(self.output_file, result)
158
159
160 def parse_arguments():
161     description = '''Process 'template_file' using 'base_file' as source for
162 template variable substitution and write the results to 'output_file'.'''
163
164     parser = ArgParser(prog='python %s' % __file__,
165                        description=description)
166     parser.add_argument('base_file',
167                         help='Base YAML filename')
168     parser.add_argument('template_file',
169                         help='Fragment filename')
170     parser.add_argument('output_file',
171                         help='Output filename')
172
173     args = parser.parse_args()
174     return(args.base_file, args.template_file, args.output_file)
175
176
177 def main():
178     base_file, template_file, output_file = parse_arguments()
179
180     templater = Templater(base_file, template_file, output_file)
181     templater.run()
182
183
184 if __name__ == '__main__':
185     main()