]>
Commit | Line | Data |
---|---|---|
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=[], fallback_machines=[], include_path=None): | |
20 | self.recipe_file = path | |
21 | try: | |
22 | self.path = os.path.dirname(self.recipe_file) | |
23 | self.path = os.path.abspath(self.path) | |
24 | self.name = os.path.basename(self.path) | |
25 | except BaseException as e: | |
26 | logger.error("Failed to get the path to this recipe") | |
27 | raise e | |
28 | ||
29 | self.log = logger.getChild(self.name) | |
30 | self.log.debug("Path of recipe is: {}".format(self.recipe_file)) | |
31 | ||
32 | # This path must be absolut | |
33 | self.include_path = include_path | |
34 | ||
35 | if self.include_path and not os.path.isabs(self.include_path): | |
36 | raise RecipeExeption("Include path must be absolut.") | |
37 | ||
38 | self.log.debug("Include path is: {}".format(self.include_path)) | |
39 | ||
40 | self._recipe = None | |
41 | self._machines = machines | |
42 | self._fallback_machines = fallback_machines | |
43 | ||
44 | self.log.debug("Machine names we use when we substitute the all statement: {}".format(self._machines)) | |
45 | ||
46 | self.log.debug("Length of the cirle list {}".format(len(circle))) | |
47 | self.in_recursion = True | |
48 | if len(circle) == 0: | |
49 | self.in_recursion = False | |
50 | ||
51 | self.log.debug("We are in a recursion: {}".format(self.in_recursion)) | |
52 | ||
53 | self.circle = circle | |
54 | self.log.debug("Recipes we have already included: {}".format(self.circle)) | |
55 | ||
56 | if not os.path.isfile(self.recipe_file): | |
57 | self.log.error("{} is not a file".format(self.recipe_file)) | |
58 | raise RecipeExeption("{} is not a file".format(self.recipe_file)) | |
59 | ||
60 | try: | |
61 | with open(self.recipe_file) as fobj: | |
62 | self.raw_recipe = fobj.readlines() | |
63 | except FileNotFoundError as error: | |
64 | self.log.error("No such file: {}".format(self.recipe_file)) | |
65 | raise error | |
66 | ||
67 | @property | |
68 | def recipe(self): | |
69 | if not self._recipe: | |
70 | self.parse() | |
71 | ||
72 | return self._recipe | |
73 | ||
74 | @property | |
75 | def machines(self): | |
76 | return self._machines | |
77 | ||
78 | def parse(self): | |
79 | self._recipe = [] | |
80 | i = 1 | |
81 | for line in self.raw_recipe: | |
82 | # Check if the line is empty | |
83 | if line.strip() == "": | |
84 | self.log.debug("Skipping empty line {}".format(i)) | |
85 | i = i + 1 | |
86 | continue | |
87 | ||
88 | # Check if the line is a comment | |
89 | if line.strip().startswith("#"): | |
90 | self.log.debug("Skipping comment in line {}".format(i)) | |
91 | i = i + 1 | |
92 | continue | |
93 | ||
94 | raw_line = line.split(":", 1) | |
95 | if len(raw_line) < 2: | |
96 | self.log.error("Error parsing the recipe in line {}".format(i)) | |
97 | raise RecipeExeption("Error parsing the recipe in line {}".format(i)) | |
98 | cmd = raw_line[1].strip() | |
99 | ||
100 | raw_line = raw_line[0].strip().split(" ") | |
101 | if len(raw_line) == 0: | |
102 | self.log.error("Failed to parse the recipe in line {}".format(i)) | |
103 | raise RecipeExeption("Failed to parse the recipe in line {}".format(i)) | |
104 | ||
105 | if raw_line[0].strip() == "": | |
106 | self.log.error("Failed to parse the recipe in line {}".format(i)) | |
107 | raise RecipeExeption("Failed to parse the recipe in line {}".format(i)) | |
108 | ||
109 | machine = raw_line[0].strip() | |
110 | ||
111 | if len(raw_line) == 2: | |
112 | extra = raw_line[1].strip() | |
113 | else: | |
114 | extra = "" | |
115 | ||
116 | # We could get a machine here or a include statement | |
117 | if machine == "include": | |
118 | path = cmd.strip() | |
119 | if self.include_path: | |
120 | path = os.path.normpath(self.include_path + "/" + path) | |
121 | else: | |
122 | path = os.path.normpath(self.path + "/" + path) | |
123 | ||
124 | # If we did not get a valid file we asume that we get a valid path to a test. | |
125 | if os.path.isdir(path): | |
126 | path = path + "/recipe" | |
127 | ||
128 | self.log.debug("Path of recipe to include is: {}".format(path)) | |
129 | ||
130 | if path in self.circle: | |
131 | self.log.error("Detect import loop!") | |
132 | raise RecipeExeption("Detect import loop!") | |
133 | self.circle.append(path) | |
134 | recipe_to_include = Recipe(path, circle=self.circle, include_path=self.include_path) | |
135 | ||
136 | if machine == "include": | |
137 | self._recipe.extend(recipe_to_include.recipe) | |
138 | else: | |
139 | # Support also something like 'alice,bob: echo' | |
140 | machines = machine.split(",") | |
141 | for machine in machines: | |
142 | self._recipe.append((machine.strip(), extra.strip(), cmd.strip())) | |
143 | ||
144 | # Increase the line number by one | |
145 | i = i + 1 | |
146 | ||
147 | # Substitue the all statement | |
148 | if not self.in_recursion: | |
149 | self.log.debug("We are not in a recursion") | |
150 | # We will store the machine names we use to substitute the all statement | |
151 | # in tmp_machines to keep the code which actually does the substitution clear | |
152 | tmp_machines = None | |
153 | ||
154 | # Check if we get a setting to substitute the all statement | |
155 | if len(self.machines) != 0: | |
156 | tmp_machines = self.machines | |
157 | ||
158 | # Second try to fill tmp_machines | |
159 | if not tmp_machines: | |
160 | # dertermine machines we use in this recipe | |
161 | tmp = [] | |
162 | for line in self.recipe: | |
163 | self.log.debug(line) | |
164 | if not line[0] in tmp and line[0] != "all": | |
165 | tmp.append(line[0]) | |
166 | ||
167 | self.log.debug("Machines except all in the recipe: {}".format(tmp)) | |
168 | ||
169 | # Check if we got anything else then all: in th recipe | |
170 | if len(tmp) != 0: | |
171 | tmp_machines = tmp | |
172 | ||
173 | # If we get here we are using all machines in the virtual environment as fallback value | |
174 | if not tmp_machines: | |
175 | tmp_machines = self._fallback_machines | |
176 | ||
177 | tmp_recipe = [] | |
178 | for line in self._recipe: | |
179 | if line[0] != "all": | |
180 | tmp_recipe.append(line) | |
181 | else: | |
182 | for machine in tmp_machines: | |
183 | tmp_recipe.append((machine.strip(), line[1], line[2])) | |
184 | ||
185 | self._recipe = tmp_recipe |