Allow including of recipe files
[nitsi.git] / src / nitsi / recipe.py
CommitLineData
3a4eb2c7
JS
1#!/usr/bin/python3
2
1ed8ca9f 3import logging
6632e137 4import os
1ed8ca9f
JS
5
6logger = logging.getLogger("nitsi.recipe")
7
3a4eb2c7
JS
8
9
10class RecipeExeption(Exception):
61b44c10
JS
11 def __init__(self, message):
12 self.message = message
3a4eb2c7
JS
13
14
15
16# Should read the test, check if the syntax are valid
17# and return tuples with the ( host, command ) structure
ee227ea1 18class Recipe():
b860b441 19 def __init__(self, path, circle=[], machines=[]):
3a4eb2c7 20 self.recipe_file = path
518441de
JS
21 try:
22 self.path = os.path.dirname(self.recipe_file)
c9c4a606 23 self.path = os.path.abspath(self.path)
518441de
JS
24 self.name = os.path.basename(self.path)
25 except BaseException as e:
c9c4a606 26 logger.error("Failed to get the path to this recipe")
518441de
JS
27 raise e
28
29 self.log = logger.getChild(self.name)
3a4eb2c7
JS
30 self.log.debug("Path of recipe is: {}".format(self.recipe_file))
31 self._recipe = None
b860b441
JS
32 self._machines = machines
33
34 self.log.debug("Machine names we use when we substitute the all statement: {}".format(self._machines))
3a4eb2c7
JS
35
36 self.in_recursion = True
37 if len(circle) == 0:
38 self.in_recursion = False
39
40 self.circle = circle
2fa4467d 41 self.log.debug("Recipes we have already included: {}".format(self.circle))
3a4eb2c7
JS
42
43 if not os.path.isfile(self.recipe_file):
faff2d5c 44 self.log.error("{} is not a file".format(self.recipe_file))
624e9083 45 raise RecipeExeption("{} is not a file".format(self.recipe_file))
3a4eb2c7
JS
46
47 try:
48 with open(self.recipe_file) as fobj:
49 self.raw_recipe = fobj.readlines()
50 except FileNotFoundError as error:
624e9083 51 self.log.error("No such file: {}".format(self.recipe_file))
61b44c10 52 raise error
3a4eb2c7
JS
53
54 @property
55 def recipe(self):
56 if not self._recipe:
57 self.parse()
58
59 return self._recipe
60
61 @property
62 def machines(self):
3a4eb2c7
JS
63 return self._machines
64
65 def parse(self):
66 self._recipe = []
67 i = 1
68 for line in self.raw_recipe:
6cb7e939
JS
69 # Check if the line is empty
70 if line.strip() == "":
71 self.log.debug("Skipping empty line {}".format(i))
72 i = i + 1
73 continue
74
75 # Check if the line is a comment
76 if line.strip().startswith("#"):
77 self.log.debug("Skipping comment in line {}".format(i))
78 i = i + 1
79 continue
80
2e8d0473 81 raw_line = line.split(":", 1)
3a4eb2c7
JS
82 if len(raw_line) < 2:
83 self.log.error("Error parsing the recipe in line {}".format(i))
61b44c10 84 raise RecipeExeption("Error parsing the recipe in line {}".format(i))
3a4eb2c7
JS
85 cmd = raw_line[1].strip()
86 raw_line = raw_line[0].strip().split(" ")
87 if len(raw_line) == 0:
88 self.log.error("Failed to parse the recipe in line {}".format(i))
61b44c10 89 raise RecipeExeption("Failed to parse the recipe in line {}".format(i))
3a4eb2c7
JS
90
91 if raw_line[0].strip() == "":
92 self.log.error("Failed to parse the recipe in line {}".format(i))
61b44c10 93 raise RecipeExeption("Failed to parse the recipe in line {}".format(i))
3a4eb2c7
JS
94
95 machine = raw_line[0].strip()
96
97 if len(raw_line) == 2:
98 extra = raw_line[1].strip()
99 else:
100 extra = ""
101
102 # We could get a machine here or a include statement
103 if machine == "include":
104 path = cmd.strip()
105 path = os.path.normpath(self.path + "/" + path)
c9c4a606
JS
106
107 # If we did not get a valid file we asume that we get a valid path to a test.
108 if os.path.isdir(path):
109 path = path + "/recipe"
110
3a4eb2c7
JS
111 if path in self.circle:
112 self.log.error("Detect import loop!")
61b44c10 113 raise RecipeExeption("Detect import loop!")
3a4eb2c7 114 self.circle.append(path)
ee227ea1 115 recipe_to_include = Recipe(path, circle=self.circle)
3a4eb2c7
JS
116
117 if machine == "include":
118 self._recipe.extend(recipe_to_include.recipe)
119 else:
120 # Support also something like 'alice,bob: echo'
121 machines = machine.split(",")
122 for machine in machines:
123 self._recipe.append((machine.strip(), extra.strip(), cmd.strip()))
124 i = i + 1
125
126 if not self.in_recursion:
127 tmp_recipe = []
128 for line in self._recipe:
129 if line[0] != "all":
130 tmp_recipe.append(line)
131 else:
132 for machine in self.machines:
133 tmp_recipe.append((machine.strip(), line[1], line[2]))
134
135 self._recipe = tmp_recipe