1 # Copyright (c) 2017 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
16 Parsing of the configuration YAML file.
21 from yaml import load, YAMLError
22 from pprint import pformat
24 from errors import PresentationError
27 class Configuration(object):
28 """Configuration of Presentation and analytics layer.
30 - based on configuration specified in the configuration YAML file
31 - presentation and analytics layer is model driven
34 # Tags are used in configuration YAML file and replaced while the file is
39 def __init__(self, cfg_file):
42 :param cfg_file: File handler for the configuration YAML file.
43 :type cfg_file: BinaryIO
45 self._cfg_file = cfg_file
48 # The configuration is stored in this directory.
49 self._configuration = {"environment": dict(),
57 def configuration(self):
58 """Getter - configuration.
60 :returns: Configuration.
63 return self._configuration
66 def environment(self):
67 """Getter - environment.
69 :returns: Environment configuration.
72 return self._configuration["environment"]
78 :returns: Debug configuration
81 return self._configuration["debug"]
85 """Getter - debug mode
87 :returns: True if debug mode is on, otherwise False.
92 if self.environment["configuration"]["CFG[DEBUG]"] == 1:
101 """Getter - configuration - inputs.
107 return self._configuration["input"]
111 """Getter - builds defined in configuration.
113 :returns: Builds defined in the configuration.
116 return self.input["builds"]
120 """Getter - configuration - output formats and versions to be generated.
122 - versions: full, ...
124 :returns: Outputs to be generated.
127 return self._configuration["output"]
131 """Getter - tables to be generated.
133 :returns: List of specifications of tables to be generated.
136 return self._configuration["tables"]
140 """Getter - plots to be generated.
142 :returns: List of specifications of plots to be generated.
145 return self._configuration["plots"]
147 def set_input_state(self, job, build_nr, state):
148 """Set the state of input
157 for build in self._configuration["input"]["builds"][job]:
158 if build["build"] == build_nr:
159 build["status"] = state
162 raise PresentationError("Build '{}' is not defined for job '{}'"
163 " in configuration file.".
164 format(build_nr, job))
166 raise PresentationError("Job '{}' and build '{}' is not defined in "
167 "configuration file.".format(job, build_nr))
169 def set_input_file_name(self, job, build_nr, file_name):
170 """Set the state of input
179 for build in self._configuration["input"]["builds"][job]:
180 if build["build"] == build_nr:
181 build["file-name"] = file_name
184 raise PresentationError("Build '{}' is not defined for job '{}'"
185 " in configuration file.".
186 format(build_nr, job))
188 raise PresentationError("Job '{}' and build '{}' is not defined in "
189 "configuration file.".format(job, build_nr))
191 def _get_type_index(self, item_type):
192 """Get index of item type (environment, input, output, ...) in
193 configuration YAML file.
195 :param item_type: Item type: Top level items in configuration YAML file,
196 e.g.: environment, input, output.
198 :returns: Index of the given item type.
203 for item in self._cfg_yaml:
204 if item["type"] == item_type:
209 def _find_tag(self, text):
210 """Find the first tag in the given text. The tag is enclosed by the
211 TAG_OPENER and TAG_CLOSER.
213 :param text: Text to be searched.
215 :returns: The tag, or None if not found.
219 start = text.index(self.TAG_OPENER)
220 end = text.index(self.TAG_CLOSER, start + 1) + 1
221 return text[start:end]
225 def _replace_tags(self, data, src_data=None):
226 """Replace tag(s) in the data by their values.
228 :param data: The data where the tags will be replaced by their values.
229 :param src_data: Data where the tags are defined. It is dictionary where
230 the key is the tag and the value is the tag value. If not given, 'data'
232 :type data: str or dict
234 :returns: Data with the tags replaced.
236 :raises: PresentationError if it is not possible to replace the tag or
237 the data is not the supported data type (str, dict).
243 if isinstance(data, str):
244 tag = self._find_tag(data)
246 data = data.replace(tag, src_data[tag[1:-1]])
248 elif isinstance(data, dict):
250 for key, value in data.items():
251 tag = self._find_tag(value)
254 data[key] = value.replace(tag, src_data[tag[1:-1]])
257 raise PresentationError("Not possible to replace the "
258 "tag '{}'".format(tag))
260 self._replace_tags(data, src_data)
262 raise PresentationError("Replace tags: Not supported data type.")
266 def _parse_env(self):
267 """Parse environment configuration in the configuration YAML file.
270 logging.info("Parsing configuration file: environment ...")
272 idx = self._get_type_index("environment")
277 self._configuration["environment"]["configuration"] = \
278 self._cfg_yaml[idx]["configuration"]
280 self._configuration["environment"]["configuration"] = None
283 self._configuration["environment"]["paths"] = \
284 self._replace_tags(self._cfg_yaml[idx]["paths"])
286 self._configuration["environment"]["paths"] = None
289 self._configuration["environment"]["urls"] = \
290 self._replace_tags(self._cfg_yaml[idx]["urls"])
292 self._configuration["environment"]["urls"] = None
295 self._configuration["environment"]["make-dirs"] = \
296 self._cfg_yaml[idx]["make-dirs"]
298 self._configuration["environment"]["make-dirs"] = None
301 self._configuration["environment"]["remove-dirs"] = \
302 self._cfg_yaml[idx]["remove-dirs"]
304 self._configuration["environment"]["remove-dirs"] = None
307 self._configuration["environment"]["build-dirs"] = \
308 self._cfg_yaml[idx]["build-dirs"]
310 self._configuration["environment"]["build-dirs"] = None
312 logging.info("Done.")
314 def _parse_debug(self):
315 """Parse debug configuration in the configuration YAML file.
318 logging.info("Parsing configuration file: debug ...")
320 idx = self._get_type_index("debug")
322 self.environment["configuration"]["CFG[DEBUG]"] = 0
326 for key, value in self._cfg_yaml[idx]["general"].items():
327 self._configuration["debug"][key] = value
329 self._configuration["input"]["builds"] = dict()
330 for job, builds in self._cfg_yaml[idx]["builds"].items():
332 self._configuration["input"]["builds"][job] = list()
334 self._configuration["input"]["builds"][job].\
335 append({"build": build["build"],
336 "status": "downloaded",
337 "file-name": self._replace_tags(
339 self.environment["paths"])})
341 logging.warning("No build is defined for the job '{}'. "
342 "Trying to continue without it.".
346 raise PresentationError("No data to process.")
348 def _parse_input(self):
349 """Parse input configuration in the configuration YAML file.
351 :raises: PresentationError if there are no data to process.
354 logging.info("Parsing configuration file: input ...")
356 idx = self._get_type_index("input")
358 raise PresentationError("No data to process.")
361 for key, value in self._cfg_yaml[idx]["general"].items():
362 self._configuration["input"][key] = value
363 self._configuration["input"]["builds"] = dict()
364 for job, builds in self._cfg_yaml[idx]["builds"].items():
366 self._configuration["input"]["builds"][job] = list()
368 self._configuration["input"]["builds"][job].\
369 append({"build": build, "status": None})
371 logging.warning("No build is defined for the job '{}'. "
372 "Trying to continue without it.".
375 raise PresentationError("No data to process.")
377 logging.info("Done.")
379 def _parse_output(self):
380 """Parse output configuration in the configuration YAML file.
382 :raises: PresentationError if there is no output defined.
385 logging.info("Parsing configuration file: output ...")
387 idx = self._get_type_index("output")
389 raise PresentationError("No output defined.")
392 self._configuration["output"] = self._cfg_yaml[idx]["format"]
394 raise PresentationError("No output defined.")
396 logging.info("Done.")
398 def _parse_elements(self):
399 """Parse elements (tables, plots) configuration in the configuration
403 logging.info("Parsing configuration file: elements ...")
406 for element in self._cfg_yaml:
408 element["output-file"] = self._replace_tags(
409 element["output-file"],
410 self._configuration["environment"]["paths"])
413 if element["type"] == "table":
414 logging.info(" {:3d} Processing a table ...".format(count))
415 self._configuration["tables"].append(element)
417 elif element["type"] == "plot":
418 logging.info(" {:3d} Processing a plot ...".format(count))
419 self._configuration["plots"].append(element)
422 logging.info("Done.")
425 """Parse configuration in the configuration YAML file.
427 :raises: PresentationError if An error occurred while parsing the
431 self._cfg_yaml = load(self._cfg_file)
432 except YAMLError as err:
433 raise PresentationError(msg="An error occurred while parsing the "
434 "configuration file.",
442 self._parse_elements()
444 logging.debug("Configuration: \n{}".
445 format(pformat(self._configuration)))