]>
Commit | Line | Data |
---|---|---|
3a4eb2c7 JS |
1 | #!/usr/bin/python3 |
2 | ||
1ed8ca9f | 3 | import logging |
6632e137 | 4 | import os |
1ed8ca9f JS |
5 | |
6 | logger = logging.getLogger("nitsi.recipe") | |
7 | ||
3a4eb2c7 JS |
8 | |
9 | ||
10 | class 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 | 18 | class Recipe(): |
ca4917b4 | 19 | def __init__(self, path, circle=[], machines=[], fallback_machines=[], include_path=None): |
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 | 30 | self.log.debug("Path of recipe is: {}".format(self.recipe_file)) |
ca4917b4 JS |
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 | ||
3a4eb2c7 | 40 | self._recipe = None |
b860b441 | 41 | self._machines = machines |
821394c3 | 42 | self._fallback_machines = fallback_machines |
b860b441 JS |
43 | |
44 | self.log.debug("Machine names we use when we substitute the all statement: {}".format(self._machines)) | |
3a4eb2c7 | 45 | |
821394c3 | 46 | self.log.debug("Length of the cirle list {}".format(len(circle))) |
3a4eb2c7 JS |
47 | self.in_recursion = True |
48 | if len(circle) == 0: | |
49 | self.in_recursion = False | |
50 | ||
821394c3 JS |
51 | self.log.debug("We are in a recursion: {}".format(self.in_recursion)) |
52 | ||
3a4eb2c7 | 53 | self.circle = circle |
2fa4467d | 54 | self.log.debug("Recipes we have already included: {}".format(self.circle)) |
3a4eb2c7 JS |
55 | |
56 | if not os.path.isfile(self.recipe_file): | |
faff2d5c | 57 | self.log.error("{} is not a file".format(self.recipe_file)) |
624e9083 | 58 | raise RecipeExeption("{} is not a file".format(self.recipe_file)) |
3a4eb2c7 JS |
59 | |
60 | try: | |
61 | with open(self.recipe_file) as fobj: | |
62 | self.raw_recipe = fobj.readlines() | |
63 | except FileNotFoundError as error: | |
624e9083 | 64 | self.log.error("No such file: {}".format(self.recipe_file)) |
61b44c10 | 65 | raise error |
3a4eb2c7 JS |
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): | |
3a4eb2c7 JS |
76 | return self._machines |
77 | ||
78 | def parse(self): | |
79 | self._recipe = [] | |
80 | i = 1 | |
81 | for line in self.raw_recipe: | |
6cb7e939 JS |
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 | ||
2e8d0473 | 94 | raw_line = line.split(":", 1) |
3a4eb2c7 JS |
95 | if len(raw_line) < 2: |
96 | self.log.error("Error parsing the recipe in line {}".format(i)) | |
61b44c10 | 97 | raise RecipeExeption("Error parsing the recipe in line {}".format(i)) |
3a4eb2c7 | 98 | cmd = raw_line[1].strip() |
821394c3 | 99 | |
3a4eb2c7 JS |
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)) | |
61b44c10 | 103 | raise RecipeExeption("Failed to parse the recipe in line {}".format(i)) |
3a4eb2c7 JS |
104 | |
105 | if raw_line[0].strip() == "": | |
106 | self.log.error("Failed to parse the recipe in line {}".format(i)) | |
61b44c10 | 107 | raise RecipeExeption("Failed to parse the recipe in line {}".format(i)) |
3a4eb2c7 JS |
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() | |
ca4917b4 JS |
119 | if self.include_path: |
120 | path = os.path.normpath(self.include_path + "/" + path) | |
121 | else: | |
122 | path = os.path.normpath(self.path + "/" + path) | |
c9c4a606 JS |
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 | ||
ca4917b4 JS |
128 | self.log.debug("Path of recipe to include is: {}".format(path)) |
129 | ||
3a4eb2c7 JS |
130 | if path in self.circle: |
131 | self.log.error("Detect import loop!") | |
61b44c10 | 132 | raise RecipeExeption("Detect import loop!") |
3a4eb2c7 | 133 | self.circle.append(path) |
ca4917b4 | 134 | recipe_to_include = Recipe(path, circle=self.circle, include_path=self.include_path) |
3a4eb2c7 JS |
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())) | |
3a4eb2c7 | 143 | |
5945ce2a JS |
144 | # Increase the line number by one |
145 | i = i + 1 | |
3a4eb2c7 | 146 | |
5945ce2a JS |
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 | ||
ca4917b4 | 185 | self._recipe = tmp_recipe |