]>
| 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 |