Change flat network name for nosdn fdio scenario
[apex-tripleo-heat-templates.git] / tools / process-templates.py
1 #!/usr/bin/env python
2 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
3 #    not use this file except in compliance with the License. You may obtain
4 #    a copy of the License at
5 #
6 #         http://www.apache.org/licenses/LICENSE-2.0
7 #
8 #    Unless required by applicable law or agreed to in writing, software
9 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 #    License for the specific language governing permissions and limitations
12 #    under the License.
13
14 import argparse
15 import jinja2
16 import os
17 import shutil
18 import six
19 import sys
20 import yaml
21
22 __tht_root_dir = os.path.dirname(os.path.dirname(__file__))
23
24
25 def parse_opts(argv):
26     parser = argparse.ArgumentParser(
27         description='Configure host network interfaces using a JSON'
28         ' config file format.')
29     parser.add_argument('-p', '--base_path', metavar='BASE_PATH',
30                         help="""base path of templates to process.""",
31                         default='.')
32     parser.add_argument('-r', '--roles-data', metavar='ROLES_DATA',
33                         help="""relative path to the roles_data.yaml file.""",
34                         default='roles_data.yaml')
35     parser.add_argument('-n', '--network-data', metavar='NETWORK_DATA',
36                         help="""relative path to the network_data.yaml file.""",
37                         default='network_data.yaml')
38     parser.add_argument('--safe',
39                         action='store_true',
40                         help="""Enable safe mode (do not overwrite files).""",
41                         default=False)
42     parser.add_argument('-o', '--output-dir', metavar='OUTPUT_DIR',
43                         help="""Output dir for all the templates""",
44                         default='')
45     opts = parser.parse_args(argv[1:])
46
47     return opts
48
49
50 def _j2_render_to_file(j2_template, j2_data, outfile_name=None,
51                        overwrite=True):
52     yaml_f = outfile_name or j2_template.replace('.j2.yaml', '.yaml')
53     print('rendering j2 template to file: %s' % outfile_name)
54
55     if not overwrite and os.path.exists(outfile_name):
56         print('ERROR: path already exists for file: %s' % outfile_name)
57         sys.exit(1)
58
59     # Search for templates relative to the current template path first
60     template_base = os.path.dirname(yaml_f)
61     j2_loader = jinja2.loaders.FileSystemLoader([template_base, __tht_root_dir])
62
63     try:
64         # Render the j2 template
65         template = jinja2.Environment(loader=j2_loader).from_string(
66             j2_template)
67         r_template = template.render(**j2_data)
68     except jinja2.exceptions.TemplateError as ex:
69         error_msg = ("Error rendering template %s : %s"
70                      % (yaml_f, six.text_type(ex)))
71         print(error_msg)
72         raise Exception(error_msg)
73     with open(outfile_name, 'w') as out_f:
74         out_f.write(r_template)
75
76
77 def process_templates(template_path, role_data_path, output_dir,
78                       network_data_path, overwrite):
79
80     with open(role_data_path) as role_data_file:
81         role_data = yaml.safe_load(role_data_file)
82
83     with open(network_data_path) as network_data_file:
84         network_data = yaml.safe_load(network_data_file)
85
86     j2_excludes_path = os.path.join(template_path, 'j2_excludes.yaml')
87     with open(j2_excludes_path) as role_data_file:
88         j2_excludes = yaml.safe_load(role_data_file)
89
90     if output_dir and not os.path.isdir(output_dir):
91         if os.path.exists(output_dir):
92             raise RuntimeError('Output dir %s is not a directory' % output_dir)
93         os.mkdir(output_dir)
94
95     role_names = [r.get('name') for r in role_data]
96     r_map = {}
97     for r in role_data:
98         r_map[r.get('name')] = r
99
100     n_map = {}
101     for n in network_data:
102         if (n.get('enabled') is not False):
103             n_map[n.get('name')] = n
104             if not n.get('name_lower'):
105                 n_map[n.get('name')]['name_lower'] = n.get('name').lower()
106         else:
107             print("skipping %s network: network is disabled" % n.get('name'))
108
109     excl_templates = ['%s/%s' % (template_path, e)
110                       for e in j2_excludes.get('name')]
111
112     if os.path.isdir(template_path):
113         for subdir, dirs, files in os.walk(template_path):
114
115             # NOTE(flaper87): Ignore hidden dirs as we don't
116             # generate templates for those.
117             # Note the slice assigment for `dirs` is necessary
118             # because we need to modify the *elements* in the
119             # dirs list rather than the reference to the list.
120             # This way we'll make sure os.walk will iterate over
121             # the shrunk list. os.walk doesn't have an API for
122             # filtering dirs at this point.
123             dirs[:] = [d for d in dirs if not d[0] == '.']
124             files = [f for f in files if not f[0] == '.']
125
126             # NOTE(flaper87): We could have used shutil.copytree
127             # but it requires the dst dir to not be present. This
128             # approach is safer as it doesn't require us to delete
129             # the output_dir in advance and it allows for running
130             # the command multiple times with the same output_dir.
131             out_dir = subdir
132             if output_dir:
133                 out_dir = os.path.join(output_dir, subdir)
134                 if not os.path.exists(out_dir):
135                     os.mkdir(out_dir)
136
137             for f in files:
138                 file_path = os.path.join(subdir, f)
139                 # We do three templating passes here:
140                 # 1. *.role.j2.yaml - we template just the role name
141                 #    and create multiple files (one per role)
142                 # 2  *.network.j2.yaml - we template the network name and
143                 #    data and create multiple files for networks and
144                 #    network ports (one per network)
145                 # 3. *.j2.yaml - we template with all roles_data,
146                 #    and create one file common to all roles
147                 if f.endswith('.role.j2.yaml'):
148                     print("jinja2 rendering role template %s" % f)
149                     with open(file_path) as j2_template:
150                         template_data = j2_template.read()
151                         print("jinja2 rendering roles %s" % ","
152                               .join(role_names))
153                         for role in role_names:
154                             j2_data = {'role': r_map[role]}
155                             out_f = "-".join(
156                                 [role.lower(),
157                                  os.path.basename(f).replace('.role.j2.yaml',
158                                                              '.yaml')])
159                             out_f_path = os.path.join(out_dir, out_f)
160                             if not (out_f_path in excl_templates):
161                                 if '{{role.name}}' in template_data:
162                                     j2_data = {'role': r_map[role],
163                                                'networks': network_data}
164                                     _j2_render_to_file(template_data, j2_data,
165                                                        out_f_path, overwrite)
166                                 else:
167                                     # Backwards compatibility with templates
168                                     # that specify {{role}} vs {{role.name}}
169                                     j2_data = {'role': role,
170                                                'networks': network_data}
171                                     # (dprince) For the undercloud installer we
172                                     # don'twant to have heat check nova/glance
173                                     # API's
174                                     if r_map[role].get('disable_constraints',
175                                                        False):
176                                         j2_data['disable_constraints'] = True
177                                     _j2_render_to_file(
178                                         template_data,j2_data,
179                                         out_f_path, overwrite)
180
181                             else:
182                                 print('skipping rendering of %s' % out_f_path)
183
184                 elif f.endswith('.network.j2.yaml'):
185                     print("jinja2 rendering network template %s" % f)
186                     with open(file_path) as j2_template:
187                         template_data = j2_template.read()
188                     print("jinja2 rendering networks %s" % ",".join(n_map))
189                     for network in n_map:
190                         j2_data = {'network': n_map[network]}
191                         # Output file names in "<name>.yaml" format
192                         out_f = os.path.basename(f).replace('.network.j2.yaml',
193                                                             '.yaml')
194                         if os.path.dirname(file_path).endswith('ports'):
195                             out_f = out_f.replace('port',
196                                                   n_map[network]['name_lower'])
197                         else:
198                             out_f = out_f.replace('network',
199                                                   n_map[network]['name_lower'])
200                         out_f_path = os.path.join(out_dir, out_f)
201                         if not (out_f_path in excl_templates):
202                             _j2_render_to_file(template_data, j2_data,
203                                                out_f_path)
204                         else:
205                             print('skipping rendering of %s' % out_f_path)
206
207                 elif f.endswith('.j2.yaml'):
208                     print("jinja2 rendering normal template %s" % f)
209                     with open(file_path) as j2_template:
210                         template_data = j2_template.read()
211                         j2_data = {'roles': role_data,
212                                    'networks': network_data}
213                         out_f = os.path.basename(f).replace('.j2.yaml', '.yaml')
214                         out_f_path = os.path.join(out_dir, out_f)
215                         _j2_render_to_file(template_data, j2_data, out_f_path,
216                                            overwrite)
217                 elif output_dir:
218                     shutil.copy(os.path.join(subdir, f), out_dir)
219
220     else:
221         print('Unexpected argument %s' % template_path)
222
223 opts = parse_opts(sys.argv)
224
225 role_data_path = os.path.join(opts.base_path, opts.roles_data)
226 network_data_path = os.path.join(opts.base_path, opts.network_data)
227
228 process_templates(opts.base_path, role_data_path, opts.output_dir,
229                   network_data_path, (not opts.safe))