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