From 5994ab9c9a75ec5cb9371cabc35bb4caa2682d26 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Wed, 26 Jun 2024 10:56:51 +0200 Subject: [PATCH] Add resource based validation --- tricot/__init__.py | 1 + tricot/main.py | 4 +- tricot/resource.py | 112 +++++++++++++++++++++++++++++++++++++++++++++ tricot/tricot.py | 19 ++++---- 4 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 tricot/resource.py diff --git a/tricot/__init__.py b/tricot/__init__.py index 5e152a1..b9be657 100644 --- a/tricot/__init__.py +++ b/tricot/__init__.py @@ -8,5 +8,6 @@ from .tricot import * from .validation import * from .extractor import * +from .resource import * name = 'tricot' diff --git a/tricot/main.py b/tricot/main.py index 3e20b6b..913a6de 100644 --- a/tricot/main.py +++ b/tricot/main.py @@ -330,8 +330,8 @@ def main(): tricot.Logger.print_with_indent_blue(str(e), e=True) sys.exit(tricot.constants.PARSER_ERROR) - except tricot.TricotRequiredFile as e: - tricot.Logger.print_mixed_yellow('Error: Test configuration requires missing file:', str(e), e=True) + except tricot.ResourceValidationException as e: + tricot.Logger.print_mixed_yellow('Error: a resource validation has failed:', str(e), e=True) tricot.Logger.print_mixed_blue('Affected configuration:', wrapper.path, e=True) sys.exit(tricot.constants.MISSING_RESOURCE) diff --git a/tricot/resource.py b/tricot/resource.py new file mode 100644 index 0000000..13eb28a --- /dev/null +++ b/tricot/resource.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import os +import pathlib +import hashlib +import requests + +from tricot.logging import Logger + + +class ResourceValidationException(Exception): + ''' + ''' + + +class Resource: + ''' + The resource class represents an additional resource that + is required for tricot to run. These can be specified within + tricot configuration files. + ''' + + def __init__(self, attrs: dict) -> None: + ''' + Initialize the resource with attributes taken from the + yaml file. + + Parameters: + attrs attributes to initialize the resource with + + Returns: + None + ''' + self.url = None + self.path = None + self.hash = None + self.mode = None + + for key, value in attrs.items(): + + if key == 'path': + self.path = value + + elif key == 'url': + self.url = value + + elif key == 'hash': + self.hash = value + + elif key == 'mode': + self.mode = value + + else: + raise ResourceValidationException('Unknown resource attribute:' + key) + + if self.path is None: + raise ResourceValidationException('Required "path" attribute is missing.') + + + def validate(self) -> None: + ''' + Validates whether the resource has the correct state. + Missing files are downloaded if url attribute was specified. + Files with incorrect permissions are adjusted. + + Parameters: + None + + Returns: + None + ''' + path = pathlib.Path(self.path).expanduser() + + if not path.is_file(): + + if not self.url: + raise ResourceValidationException(f'{path}: does not exist.') + + Logger.print_mixed_yellow('Downloading missing resource from:', self.url) + r = requests.get(self.url) + + if r.status_code != 200: + raise ResourceValidationException(f'{self.url}: did not return 200.') + + Logger.print_mixed_blue('Writing resource data to:', str(path)) + path.write_bytes(r.content) + + if self.mode: + Logger.print_mixed_blue('Adjusting permissions of resource to:', self.mode) + os.chmod(path, int(self.mode, 8)) + + if self.hash: + + triggered = False + content = path.read_bytes() + + for hash_type in ['md5', 'sha1', 'sha256', 'sha512']: + + hash_value = self.hash.get(hash_type) + + if hash_value is not None: + + triggered = True + computed = hashlib.new(hash_type, content).hexdigest() + + if computed != hash_value: + raise ResourceValidationException(f'{path}: {computed} != {hash_value}') + + if not triggered: + raise ResourceValidationException('hash attribute contains no valid hash types.') diff --git a/tricot/tricot.py b/tricot/tricot.py index 2b35839..8edb5bf 100644 --- a/tricot/tricot.py +++ b/tricot/tricot.py @@ -15,6 +15,7 @@ from tricot.plugin import Plugin from tricot.command import Command from tricot.condition import Condition +from tricot.resource import Resource skip_until = None @@ -40,12 +41,6 @@ def __init__(self, message: str, path: Path = None) -> None: super().__init__(message) -class TricotRequiredFile(Exception): - ''' - Custom exception class for a missing required file. - ''' - - class TricotRequiredCommand(Exception): ''' Custom exception class for a missing required command. @@ -864,13 +859,15 @@ def check_requirements(self): for file in requires.get('files', []): - if type(file) is str and not Path(file).exists(): - raise ExceptionWrapper(TricotRequiredFile(file), self.path) + try: + if type(file) is str: + Resource({'path': file}).validate() - elif type(file) is dict: + elif type(file) is dict: + Resource(file).validate() - if not tricot.utils.file_integrity(file): - raise ExceptionWrapper(TricotRequiredFile(file.get('filename') + " (wrong hash value)"), self.path) + except Exception as e: + raise ExceptionWrapper(e, self.path) for command in requires.get('commands', []):