From 3ede554a22bc33f48e0a9b8c528b5747ad6b0d3d Mon Sep 17 00:00:00 2001 From: Shane Brown Date: Wed, 23 Aug 2023 11:01:21 -0600 Subject: [PATCH] Rework config validation format --- buildrunner/config_model.py | 195 ++++++++++++++++++------------------ 1 file changed, 99 insertions(+), 96 deletions(-) diff --git a/buildrunner/config_model.py b/buildrunner/config_model.py index 1659adff..23bdd0ed 100644 --- a/buildrunner/config_model.py +++ b/buildrunner/config_model.py @@ -8,7 +8,7 @@ from typing import Dict, List, Optional, Set, Union -from pydantic import BaseModel # pylint: disable=no-name-in-module +from pydantic import BaseModel, validator # pylint: disable=no-name-in-module class StepBuild(BaseModel): @@ -38,117 +38,120 @@ def is_multi_platform(self): return self.build is not None and \ self.build.platforms is not None + # def has_multi_platform_build(self): + # """ + # Check if the config file has multi-platform build steps + + # Returns: + # bool: True if the config file has multi-platform build steps, False otherwise + # """ + # if self.steps is None: + # return False + + # for step in self.steps.values(): + # if step.is_multi_platform(): + # return True + # return False + class Config(BaseModel): """ Top level config model """ version: Optional[float] steps: Optional[Dict[str, Step]] - def __init__(self, **data) -> None: - super().__init__(**data) - self.validate() - - def has_multi_platform_build(self): - """ - Check if the config file has multi-platform build steps - - Returns: - bool: True if the config file has multi-platform build steps, False otherwise - """ - if self.steps is None: - return False - - for step in self.steps.values(): - if step.is_multi_platform(): - return True - return False - - def validate_push(self, - push: Union[StepPushDict, List[Union[str, StepPushDict]], str], - mp_push_tags: Set[str], - step_name: str, - update_mp_push_tags: bool = True): - """ - Validate push step - - Args: - push (StepPushDict | list[str | StepPushDict] | str): Push step - mp_push_tags (Set[str]): Set of all tags used in multi-platform build steps - step_name (str): Name of the step - update_mp_push_tags (bool, optional): Whether to update the set of tags used in multi-platform steps. - - Raises: - ValueError: If the config file is invalid - """ - # Check for valid push section, duplicate mp tags are not allowed - if push is not None: - name = None - names = None - if isinstance(push, str): - name = push - if ":" not in name: - name = f'{name}:latest' - - if isinstance(push, StepPushDict): - names = [f"{push.repository}:{tag}" for tag in push.tags] - - if names is not None: - for current_name in names: - if current_name in mp_push_tags: - # raise ValueError(f'Cannot specify duplicate tag {current_name} in build step {step_name}') - raise ValueError(f'Cannot specify duplicate tag {current_name} in build step {step_name}') - - if name is not None and name in mp_push_tags: - # raise ValueError(f'Cannot specify duplicate tag {name} in build step {step_name}') - raise ValueError(f'Cannot specify duplicate tag {name} in build step {step_name}') - - if update_mp_push_tags and names is not None: - mp_push_tags.update(names) - - if update_mp_push_tags and name is not None: - mp_push_tags.add(name) - - def validate_multi_platform_build(self, mp_push_tags: Set[str]): - """ - Validate multi-platform build steps - - Args: - mp_push_tags (Set[str]): Set of all tags used in multi-platform build steps - - Raises: - ValueError | pydantic.ValidationError: If the config file is invalid - """ - # Iterate through each step - for step_name, step in self.steps.items(): - if step.is_multi_platform(): - if step.build.platform is not None: - raise ValueError(f'Cannot specify both platform ({step.build.platform}) and ' - f'platforms ({step.build.platforms}) in build step {step_name}') - - if not isinstance(step.build.platforms, list): - raise ValueError(f'platforms must be a list in build step {step_name}') - - # Check for valid push section, duplicate mp tags are not allowed - self.validate_push(step.push, mp_push_tags, step_name) - - def validate(self): + # Note this is pydantic version 1.10 syntax + @validator('steps') + def validate_steps(cls, values) -> None: """ Validate the config file Raises: ValueError | pydantic.ValidationError : If the config file is invalid """ - if self.has_multi_platform_build(): + + def validate_push(push: Union[StepPushDict, List[Union[str, StepPushDict]], str], + mp_push_tags: Set[str], + step_name: str, + update_mp_push_tags: bool = True): + """ + Validate push step + + Args: + push (StepPushDict | list[str | StepPushDict] | str): Push step + mp_push_tags (Set[str]): Set of all tags used in multi-platform build steps + step_name (str): Name of the step + update_mp_push_tags (bool, optional): Whether to update the set of tags used in multi-platform steps. + + Raises: + ValueError: If the config file is invalid + """ + # Check for valid push section, duplicate mp tags are not allowed + if push is not None: + name = None + names = None + if isinstance(push, str): + name = push + if ":" not in name: + name = f'{name}:latest' + + if isinstance(push, StepPushDict): + names = [f"{push.repository}:{tag}" for tag in push.tags] + + if names is not None: + for current_name in names: + if current_name in mp_push_tags: + # raise ValueError(f'Cannot specify duplicate tag {current_name} in build step {step_name}') + raise ValueError(f'Cannot specify duplicate tag {current_name} in build step {step_name}') + + if name is not None and name in mp_push_tags: + # raise ValueError(f'Cannot specify duplicate tag {name} in build step {step_name}') + raise ValueError(f'Cannot specify duplicate tag {name} in build step {step_name}') + + if update_mp_push_tags and names is not None: + mp_push_tags.update(names) + + if update_mp_push_tags and name is not None: + mp_push_tags.add(name) + + def validate_multi_platform_build(mp_push_tags: Set[str]): + """ + Validate multi-platform build steps + + Args: + mp_push_tags (Set[str]): Set of all tags used in multi-platform build steps + + Raises: + ValueError | pydantic.ValidationError: If the config file is invalid + """ + # Iterate through each step + for step_name, step in values.items(): + if step.is_multi_platform(): + if step.build.platform is not None: + raise ValueError(f'Cannot specify both platform ({step.build.platform}) and ' + f'platforms ({step.build.platforms}) in build step {step_name}') + + if not isinstance(step.build.platforms, list): + raise ValueError(f'platforms must be a list in build step {step_name}') + + # Check for valid push section, duplicate mp tags are not allowed + validate_push(step.push, mp_push_tags, step_name) + + has_multi_platform_build = False + for step in values.values(): + has_multi_platform_build = has_multi_platform_build or step.is_multi_platform() + + if has_multi_platform_build: mp_push_tags = set() - self.validate_multi_platform_build(mp_push_tags) + validate_multi_platform_build(mp_push_tags) # Validate that all tags are unique across all multi-platform step - for step_name, step in self.steps.items(): + for step_name, step in values.items(): # Check that there are no single platform tags that match multi-platform tags if not step.is_multi_platform(): if step.push is not None: - self.validate_push(push=step.push, - mp_push_tags=mp_push_tags, - step_name=step_name, - update_mp_push_tags=False) + validate_push(push=step.push, + mp_push_tags=mp_push_tags, + step_name=step_name, + update_mp_push_tags=False) + return values