From 90104c50f17bca2a8d1e3806a8c21453a34c3e1f Mon Sep 17 00:00:00 2001 From: pyth0n1c Date: Fri, 6 Dec 2024 18:00:57 -0800 Subject: [PATCH 1/8] Most of the way there but not yet. --- contentctl/contentctl.py | 10 +++-- contentctl/objects/config.py | 70 +++++++++++++++++++++++++++++++-- contentctl/output/yml_writer.py | 5 +++ 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/contentctl/contentctl.py b/contentctl/contentctl.py index efef5853..58e71bf7 100644 --- a/contentctl/contentctl.py +++ b/contentctl/contentctl.py @@ -21,7 +21,8 @@ from contentctl.input.yml_reader import YmlReader from contentctl.actions.deploy_acs import Deploy from contentctl.actions.release_notes import ReleaseNotes - +import importlib.metadata +from semantic_version import Version # def print_ascii_art(): # print( # """ @@ -55,7 +56,9 @@ -def init_func(config:test): +def init_func(config:test): + print("test here") + config.contentctl_library_version = Version(importlib.metadata.version('contentctl')) Initialize().execute(config) @@ -139,6 +142,7 @@ def main(): # We MUST load a config (with testing info) object so that we can # properly construct the command line, including 'contentctl test' parameters. if not configFile.is_file(): + extras_dict = {'contentctl_library_version': importlib.metadata.version('contentctl')} if "init" not in sys.argv and "--help" not in sys.argv and "-h" not in sys.argv: raise Exception(f"'{configFile}' not found in the current directory.\n" "Please ensure you are in the correct directory or run 'contentctl init' to create a new content pack.") @@ -149,7 +153,7 @@ def main(): # Otherwise generate a stub config file. # It will be used during init workflow - t = test() + t = test(**extras_dict) config_obj = t.model_dump() else: diff --git a/contentctl/objects/config.py b/contentctl/objects/config.py index 659d1113..9aafd001 100644 --- a/contentctl/objects/config.py +++ b/contentctl/objects/config.py @@ -9,9 +9,9 @@ from urllib.parse import urlparse from abc import ABC, abstractmethod from functools import partialmethod - +import importlib.metadata import tqdm -import semantic_version +from semantic_version import Version from pydantic import ( BaseModel, Field, field_validator, field_serializer, ConfigDict, DirectoryPath, @@ -139,7 +139,7 @@ class CustomApp(App_Base): @field_validator('version') def validate_version(cls, v, values): try: - _ = semantic_version.Version(v) + _ = Version(v) except Exception as e: raise(ValueError(f"The specified version does not follow the semantic versioning spec (https://semver.org/). {str(e)}")) return v @@ -169,6 +169,70 @@ class Config_Base(BaseModel): "This option makes debugging contentctl errors much easier, but produces way more " "output than is useful under most uses cases. " "Please use this flag if you are submitting a bug report or issue on GitHub.") + contentctl_library_version: Version = Field(description="Different versions of " + "contentctl may produce different application builds. " + "For that reason, the version of contentctl MUST be " + "specified in the contentctl.yml file. If this version " + "does not match the installed version EXACTLY, " + "then an exception will be generated.") + #Make sure the type of the field is correct + @field_validator('contentctl_library_version', mode='before') + def convertToSemver(cls,v:str | Version, values:Any)->Version: + if not isinstance(v, Version): + return Version(v) + return v + + @model_validator(mode="after") + def ensureProperVersionOfContentCtlAndPresenceOfRequirements(self)->Self: + ''' + Different versions of contentctl may result in builds of the same underlying content + having different contents (fields in conf files, app structure, etc). For that reason, + the state of the app MUST be associated with an exact contentctl version. This function + ensures that: + - This version of contentctl matches the version defined in contentctl_library_version EXACTLY + - the requirements.txt file exists in the root of the repo (and also has the same version). This makes CI/CD scripts far easier to develop. + + If either of these checks are not met, then contentctl will exit with a verbose error message. + ''' + + installed_contentctl_version = Version(importlib.metadata.version('contentctl')) + + #Ensure that requirements.txt exists in the correct location + # and contains the EXACT appropriate version string + requirements_file = self.path/'requirements.txt' + + if not requirements_file.is_file(): + raise Exception(f"Error: The file '{requirements_file}' does not exist. " + f"This file MUST exist and contain the exact line to match the field 'contentctl_library_version' in contentctl.yml:" + f"\ncontentctl=={self.contentctl_library_version}") + + with requirements_file.open('r') as requirements_file_handle: + requirements_file_data = requirements_file_handle.read() + import re + requirements_contentctl_version_match = re.findall(r'contentctl==([^\s]*)', requirements_file_data) + if len(requirements_contentctl_version_match) != 1: + raise Exception(f"Error: Failed to find exactly one version of contentctl in the '{requirements_file}' does not exist. \n" + f"This file MUST contain the exact line to match the field 'contentctl_library_version' in contentctl.yml:" + f"\ncontentctl=={self.contentctl_library_version}") + + try: + requirements_contentctl_version = Version(requirements_contentctl_version_match[0]) + except Exception: + raise Exception(f"The version of contentctl specified in {requirements_file} is " + f"not a valid semantic_version: '{requirements_contentctl_version_match[0]}'") + + if installed_contentctl_version == requirements_contentctl_version == self.contentctl_library_version: + return self + + + raise Exception("There is a mismatch between the installed version of contentctl and required version of contentctl. These values MUST match:" + f"\n Installed contentctl: [{installed_contentctl_version}]" + f"\n requirements.txt contentctl: [{requirements_contentctl_version}]" + f"\n contentctl_library_version defined in contentctl.yml: [{self.contentctl_library_version}]" + "\nPlease bring these versions into alignment.") + + + @field_serializer('path',when_used='always') def serialize_path(path: DirectoryPath)->str: diff --git a/contentctl/output/yml_writer.py b/contentctl/output/yml_writer.py index 7d71762b..27758eb0 100644 --- a/contentctl/output/yml_writer.py +++ b/contentctl/output/yml_writer.py @@ -1,6 +1,11 @@ import yaml from typing import Any +from semantic_version import Version +yaml.SafeDumper.add_multi_representer( + Version, + yaml.representer.SafeRepresenter.represent_str +) class YmlWriter: From 8a2a93c36fa738705905ae8dde3f72ec192a39fe Mon Sep 17 00:00:00 2001 From: pyth0n1c Date: Mon, 9 Dec 2024 10:13:00 -0800 Subject: [PATCH 2/8] Save these changes to make diffing the changes easier --- contentctl/objects/config.py | 73 ++++++++++++++++++++++----------- contentctl/output/yml_writer.py | 6 +-- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/contentctl/objects/config.py b/contentctl/objects/config.py index 9aafd001..0bb95182 100644 --- a/contentctl/objects/config.py +++ b/contentctl/objects/config.py @@ -12,6 +12,7 @@ import importlib.metadata import tqdm from semantic_version import Version +import re from pydantic import ( BaseModel, Field, field_validator, field_serializer, ConfigDict, DirectoryPath, @@ -175,7 +176,11 @@ class Config_Base(BaseModel): "specified in the contentctl.yml file. If this version " "does not match the installed version EXACTLY, " "then an exception will be generated.") - #Make sure the type of the field is correct + + + + + @field_validator('contentctl_library_version', mode='before') def convertToSemver(cls,v:str | Version, values:Any)->Version: if not isinstance(v, Version): @@ -208,7 +213,6 @@ def ensureProperVersionOfContentCtlAndPresenceOfRequirements(self)->Self: with requirements_file.open('r') as requirements_file_handle: requirements_file_data = requirements_file_handle.read() - import re requirements_contentctl_version_match = re.findall(r'contentctl==([^\s]*)', requirements_file_data) if len(requirements_contentctl_version_match) != 1: raise Exception(f"Error: Failed to find exactly one version of contentctl in the '{requirements_file}' does not exist. \n" @@ -233,6 +237,9 @@ def ensureProperVersionOfContentCtlAndPresenceOfRequirements(self)->Self: + @field_serializer('contentctl_library_version',when_used='always') + def serialize_verison(contentctl_library_version: Version)->str: + return str(contentctl_library_version) @field_serializer('path',when_used='always') def serialize_path(path: DirectoryPath)->str: @@ -246,6 +253,31 @@ class init(Config_Base): "init will still create the directory structure of the app, " "include the app_template directory with default content, and content in " "the deployment/ directory (since it is not yet easily customizable).") + + @model_validator(mode='before') + @classmethod + def ensureRequiremetsDotTextExists(cls, data:Any, info: ValidationInfo)->Any: + ''' + The requirements.txt file MUST exist in the path. If it does not, create it + in the correct path with the contents of the current version of + contentctil which is installed. + ''' + print(data) + print(info) + path:pathlib.Path | None = data.get("path", None) + if path is None: + raise Exception("'path' not provided for contentctl.yml") + + requirements_file_path = path/'requirements.txt' + if pathlib.Path(requirements_file_path).is_file(): + return data + + print(f"requirements.txt not found in '{path}'. Creating it...") + with open(requirements_file_path, 'w') as req_file: + version_line = f"contentctl=={importlib.metadata.version('contentctl')}\n" + req_file.write(version_line) + + return data # TODO (#266): disable the use_enum_values configuration @@ -314,29 +346,18 @@ class build(validate): def serialize_build_path(path: DirectoryPath)->str: return str(path) - @field_validator('build_path',mode='before') - @classmethod - def ensure_build_path(cls, v:Union[str,DirectoryPath]): - ''' - If the build path does not exist, then create it. - If the build path is actually a file, then raise a descriptive - exception. - ''' - if isinstance(v,str): - v = pathlib.Path(v) - if v.is_dir(): - return v - elif v.is_file(): - raise ValueError(f"Build path {v} must be a directory, but instead it is a file") - elif not v.exists(): - v.mkdir(parents=True) - return v def getBuildDir(self)->pathlib.Path: - return self.path / self.build_path + p = self.path / self.build_path + if not p.is_dir(): + p.mkdir(parents=True) + return p def getPackageDirectoryPath(self)->pathlib.Path: - return self.getBuildDir() / f"{self.app.appid}" + p = self.getBuildDir() / f"{self.app.appid}" + if not p.is_dir(): + p.mkdir(parents=True) + return p def getPackageFilePath(self, include_version:bool=False)->pathlib.Path: @@ -346,10 +367,16 @@ def getPackageFilePath(self, include_version:bool=False)->pathlib.Path: return self.getBuildDir() / f"{self.app.appid}-latest.tar.gz" def getAPIPath(self)->pathlib.Path: - return self.getBuildDir() / "api" + p = self.getBuildDir() / "api" + if not p.is_dir(): + p.mkdir(parents=True) + return p def getAppTemplatePath(self)->pathlib.Path: - return self.path/"app_template" + p = self.path/"app_template" + if not p.is_dir(): + p.mkdir(parents=True) + return p class StackType(StrEnum): diff --git a/contentctl/output/yml_writer.py b/contentctl/output/yml_writer.py index 27758eb0..7d45d39e 100644 --- a/contentctl/output/yml_writer.py +++ b/contentctl/output/yml_writer.py @@ -1,11 +1,7 @@ import yaml from typing import Any -from semantic_version import Version -yaml.SafeDumper.add_multi_representer( - Version, - yaml.representer.SafeRepresenter.represent_str -) + class YmlWriter: From b2eb654c515790ae032f5d36328f058ec22bc517 Mon Sep 17 00:00:00 2001 From: pyth0n1c Date: Thu, 12 Dec 2024 15:27:50 -0800 Subject: [PATCH 3/8] Enforce the correct version of contentctl is used for running contentctl ops --- contentctl/contentctl.py | 13 ++-- contentctl/objects/config.py | 118 +++++++++++------------------------ 2 files changed, 43 insertions(+), 88 deletions(-) diff --git a/contentctl/contentctl.py b/contentctl/contentctl.py index 58e71bf7..42806e38 100644 --- a/contentctl/contentctl.py +++ b/contentctl/contentctl.py @@ -21,8 +21,8 @@ from contentctl.input.yml_reader import YmlReader from contentctl.actions.deploy_acs import Deploy from contentctl.actions.release_notes import ReleaseNotes -import importlib.metadata from semantic_version import Version +import importlib.metadata # def print_ascii_art(): # print( # """ @@ -56,9 +56,7 @@ -def init_func(config:test): - print("test here") - config.contentctl_library_version = Version(importlib.metadata.version('contentctl')) +def init_func(config:test): Initialize().execute(config) @@ -142,7 +140,6 @@ def main(): # We MUST load a config (with testing info) object so that we can # properly construct the command line, including 'contentctl test' parameters. if not configFile.is_file(): - extras_dict = {'contentctl_library_version': importlib.metadata.version('contentctl')} if "init" not in sys.argv and "--help" not in sys.argv and "-h" not in sys.argv: raise Exception(f"'{configFile}' not found in the current directory.\n" "Please ensure you are in the correct directory or run 'contentctl init' to create a new content pack.") @@ -152,8 +149,8 @@ def main(): "Please ensure that contentctl.yml exists by manually creating it or running 'contentctl init'") # Otherwise generate a stub config file. # It will be used during init workflow - - t = test(**extras_dict) + from semantic_version import Version + t = test(contentctl_library_version=Version(importlib.metadata.version('contentctl'))) config_obj = t.model_dump() else: @@ -196,6 +193,8 @@ def main(): with warnings.catch_warnings(action="ignore"): config = tyro.cli(models) + #Make sure the right version of contentctl is installed + config.ensureProperVersionOfContentCtl() if type(config) == init: t.__dict__.update(config.__dict__) diff --git a/contentctl/objects/config.py b/contentctl/objects/config.py index 0bb95182..66504216 100644 --- a/contentctl/objects/config.py +++ b/contentctl/objects/config.py @@ -170,25 +170,14 @@ class Config_Base(BaseModel): "This option makes debugging contentctl errors much easier, but produces way more " "output than is useful under most uses cases. " "Please use this flag if you are submitting a bug report or issue on GitHub.") - contentctl_library_version: Version = Field(description="Different versions of " - "contentctl may produce different application builds. " - "For that reason, the version of contentctl MUST be " - "specified in the contentctl.yml file. If this version " - "does not match the installed version EXACTLY, " - "then an exception will be generated.") - - - - - - @field_validator('contentctl_library_version', mode='before') - def convertToSemver(cls,v:str | Version, values:Any)->Version: - if not isinstance(v, Version): - return Version(v) - return v + contentctl_library_version: str = Field(description="Different versions of " + "contentctl may produce different application builds. " + "For that reason, the version of contentctl MUST be " + "specified in the contentctl.yml file. If this version " + "does not match the installed version EXACTLY, " + "then an exception will be generated.") - @model_validator(mode="after") - def ensureProperVersionOfContentCtlAndPresenceOfRequirements(self)->Self: + def ensureProperVersionOfContentCtl(self)->Self: ''' Different versions of contentctl may result in builds of the same underlying content having different contents (fields in conf files, app structure, etc). For that reason, @@ -202,38 +191,21 @@ def ensureProperVersionOfContentCtlAndPresenceOfRequirements(self)->Self: installed_contentctl_version = Version(importlib.metadata.version('contentctl')) - #Ensure that requirements.txt exists in the correct location - # and contains the EXACT appropriate version string - requirements_file = self.path/'requirements.txt' - - if not requirements_file.is_file(): - raise Exception(f"Error: The file '{requirements_file}' does not exist. " - f"This file MUST exist and contain the exact line to match the field 'contentctl_library_version' in contentctl.yml:" - f"\ncontentctl=={self.contentctl_library_version}") - - with requirements_file.open('r') as requirements_file_handle: - requirements_file_data = requirements_file_handle.read() - requirements_contentctl_version_match = re.findall(r'contentctl==([^\s]*)', requirements_file_data) - if len(requirements_contentctl_version_match) != 1: - raise Exception(f"Error: Failed to find exactly one version of contentctl in the '{requirements_file}' does not exist. \n" - f"This file MUST contain the exact line to match the field 'contentctl_library_version' in contentctl.yml:" - f"\ncontentctl=={self.contentctl_library_version}") - - try: - requirements_contentctl_version = Version(requirements_contentctl_version_match[0]) - except Exception: - raise Exception(f"The version of contentctl specified in {requirements_file} is " - f"not a valid semantic_version: '{requirements_contentctl_version_match[0]}'") - - if installed_contentctl_version == requirements_contentctl_version == self.contentctl_library_version: + if installed_contentctl_version == self.contentctl_library_version: return self raise Exception("There is a mismatch between the installed version of contentctl and required version of contentctl. These values MUST match:" f"\n Installed contentctl: [{installed_contentctl_version}]" - f"\n requirements.txt contentctl: [{requirements_contentctl_version}]" f"\n contentctl_library_version defined in contentctl.yml: [{self.contentctl_library_version}]" - "\nPlease bring these versions into alignment.") + "\nPlease bring these versions into alignment.\n\n" + "If you are using contentctl in a CI/CD context, it may be " + "helpful to use the following command to install the version " + "of contentctl specified in the YML file:\n" + f''' * pip install "contentctl==$(grep 'contentctl_library_version' {self.path/'contentctl.yml'} | cut -d':' -f2- | sed 's/ //')"\n\n''' + #"Or, if you are hosting a custom version of contentctl in a git repo, for example at with 'contentctl_library_version: git+https://github.com/splunk/contentctl':\n" + #f''' * pip install "$(grep 'contentctl_library_version' {self.path/'contentctl.yml'} | cut -d':' -f2- | sed 's/ //')"''' + ) @@ -254,31 +226,6 @@ class init(Config_Base): "include the app_template directory with default content, and content in " "the deployment/ directory (since it is not yet easily customizable).") - @model_validator(mode='before') - @classmethod - def ensureRequiremetsDotTextExists(cls, data:Any, info: ValidationInfo)->Any: - ''' - The requirements.txt file MUST exist in the path. If it does not, create it - in the correct path with the contents of the current version of - contentctil which is installed. - ''' - print(data) - print(info) - path:pathlib.Path | None = data.get("path", None) - if path is None: - raise Exception("'path' not provided for contentctl.yml") - - requirements_file_path = path/'requirements.txt' - if pathlib.Path(requirements_file_path).is_file(): - return data - - print(f"requirements.txt not found in '{path}'. Creating it...") - with open(requirements_file_path, 'w') as req_file: - version_line = f"contentctl=={importlib.metadata.version('contentctl')}\n" - req_file.write(version_line) - - return data - # TODO (#266): disable the use_enum_values configuration class validate(Config_Base): @@ -347,17 +294,29 @@ def serialize_build_path(path: DirectoryPath)->str: return str(path) + @field_validator('build_path',mode='before') + @classmethod + def ensure_build_path(cls, v:Union[str,DirectoryPath]): + ''' + If the build path does not exist, then create it. + If the build path is actually a file, then raise a descriptive + exception. + ''' + if isinstance(v,str): + v = pathlib.Path(v) + if v.is_dir(): + return v + elif v.is_file(): + raise ValueError(f"Build path {v} must be a directory, but instead it is a file") + elif not v.exists(): + v.mkdir(parents=True) + return v + def getBuildDir(self)->pathlib.Path: - p = self.path / self.build_path - if not p.is_dir(): - p.mkdir(parents=True) - return p + return self.path / self.build_path def getPackageDirectoryPath(self)->pathlib.Path: - p = self.getBuildDir() / f"{self.app.appid}" - if not p.is_dir(): - p.mkdir(parents=True) - return p + return self.getBuildDir() / f"{self.app.appid}" def getPackageFilePath(self, include_version:bool=False)->pathlib.Path: @@ -367,10 +326,7 @@ def getPackageFilePath(self, include_version:bool=False)->pathlib.Path: return self.getBuildDir() / f"{self.app.appid}-latest.tar.gz" def getAPIPath(self)->pathlib.Path: - p = self.getBuildDir() / "api" - if not p.is_dir(): - p.mkdir(parents=True) - return p + return self.getBuildDir() / "api" def getAppTemplatePath(self)->pathlib.Path: p = self.path/"app_template" From f7915736cfeaaf5249e1b4df16e438b2be96d7dc Mon Sep 17 00:00:00 2001 From: pyth0n1c Date: Thu, 12 Dec 2024 15:45:47 -0800 Subject: [PATCH 4/8] fix type for version --- contentctl/contentctl.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contentctl/contentctl.py b/contentctl/contentctl.py index 42806e38..02f487d8 100644 --- a/contentctl/contentctl.py +++ b/contentctl/contentctl.py @@ -21,7 +21,6 @@ from contentctl.input.yml_reader import YmlReader from contentctl.actions.deploy_acs import Deploy from contentctl.actions.release_notes import ReleaseNotes -from semantic_version import Version import importlib.metadata # def print_ascii_art(): # print( @@ -149,8 +148,7 @@ def main(): "Please ensure that contentctl.yml exists by manually creating it or running 'contentctl init'") # Otherwise generate a stub config file. # It will be used during init workflow - from semantic_version import Version - t = test(contentctl_library_version=Version(importlib.metadata.version('contentctl'))) + t = test(contentctl_library_version=importlib.metadata.version('contentctl')) config_obj = t.model_dump() else: @@ -195,7 +193,7 @@ def main(): #Make sure the right version of contentctl is installed config.ensureProperVersionOfContentCtl() - + if type(config) == init: t.__dict__.update(config.__dict__) init_func(t) From 61b4416f5209b87c17f34d0b2fe1bf0343f2890c Mon Sep 17 00:00:00 2001 From: pyth0n1c Date: Thu, 12 Dec 2024 15:54:19 -0800 Subject: [PATCH 5/8] fix version typing --- contentctl/objects/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contentctl/objects/config.py b/contentctl/objects/config.py index d395d1e1..a941e69a 100644 --- a/contentctl/objects/config.py +++ b/contentctl/objects/config.py @@ -185,7 +185,7 @@ def ensureProperVersionOfContentCtl(self)->Self: If either of these checks are not met, then contentctl will exit with a verbose error message. ''' - installed_contentctl_version = Version(importlib.metadata.version('contentctl')) + installed_contentctl_version = importlib.metadata.version('contentctl') if installed_contentctl_version == self.contentctl_library_version: return self From 9b373526278837a8261eb3acde5473720f653218 Mon Sep 17 00:00:00 2001 From: pyth0n1c Date: Thu, 12 Dec 2024 16:06:25 -0800 Subject: [PATCH 6/8] add version feature to contentctl. make sure testing against escu uses the current version for enforcement, overwriting what may be in contentctl.yml --- .github/workflows/test_against_escu.yml | 2 +- contentctl/contentctl.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_against_escu.yml b/.github/workflows/test_against_escu.yml index ef07d445..ed5956f4 100644 --- a/.github/workflows/test_against_escu.yml +++ b/.github/workflows/test_against_escu.yml @@ -64,7 +64,7 @@ jobs: - name: Run contentctl build run: | cd security_content - poetry run contentctl build --enrichments + poetry run contentctl build --enrichments --contentctl_library_version $(poetry run contentctl --version | sed "s/contentctl //") # Do not run a test - it will take far too long! # Do not upload any artifacts diff --git a/contentctl/contentctl.py b/contentctl/contentctl.py index b4afd64a..0130a354 100644 --- a/contentctl/contentctl.py +++ b/contentctl/contentctl.py @@ -133,6 +133,10 @@ def test_common_func(config:test_common): raise Exception("There was at least one unsuccessful test") def main(): + if "--version" in sys.argv: + print(f"contentctl {importlib.metadata.version('contentctl')}") + sys.exit(0) + try: configFile = pathlib.Path("contentctl.yml") From 4f010e3f0e6fbb9fabbaacaf0f46b50b904688d2 Mon Sep 17 00:00:00 2001 From: pyth0n1c Date: Thu, 12 Dec 2024 16:17:31 -0800 Subject: [PATCH 7/8] use current version unless it is explicitly over-ridden --- contentctl/objects/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contentctl/objects/config.py b/contentctl/objects/config.py index a941e69a..f8748f39 100644 --- a/contentctl/objects/config.py +++ b/contentctl/objects/config.py @@ -166,7 +166,8 @@ class Config_Base(BaseModel): "This option makes debugging contentctl errors much easier, but produces way more " "output than is useful under most uses cases. " "Please use this flag if you are submitting a bug report or issue on GitHub.") - contentctl_library_version: str = Field(description="Different versions of " + contentctl_library_version: str = Field(default=importlib.metadata.version('contentctl'), + description="Different versions of " "contentctl may produce different application builds. " "For that reason, the version of contentctl MUST be " "specified in the contentctl.yml file. If this version " From aa1ce141ba6f371f5c7fd28110585e184f117659 Mon Sep 17 00:00:00 2001 From: pyth0n1c Date: Thu, 12 Dec 2024 16:39:56 -0800 Subject: [PATCH 8/8] make a validator instead of needing to call expllicitly --- contentctl/contentctl.py | 5 +---- contentctl/objects/config.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/contentctl/contentctl.py b/contentctl/contentctl.py index 0130a354..33b1e825 100644 --- a/contentctl/contentctl.py +++ b/contentctl/contentctl.py @@ -152,7 +152,7 @@ def main(): "Please ensure that contentctl.yml exists by manually creating it or running 'contentctl init'") # Otherwise generate a stub config file. # It will be used during init workflow - t = test(contentctl_library_version=importlib.metadata.version('contentctl')) + t = test() config_obj = t.model_dump() else: @@ -195,9 +195,6 @@ def main(): with warnings.catch_warnings(action="ignore"): config = tyro.cli(models) - #Make sure the right version of contentctl is installed - config.ensureProperVersionOfContentCtl() - if type(config) == init: t.__dict__.update(config.__dict__) init_func(t) diff --git a/contentctl/objects/config.py b/contentctl/objects/config.py index f8748f39..af09d3b7 100644 --- a/contentctl/objects/config.py +++ b/contentctl/objects/config.py @@ -173,7 +173,7 @@ class Config_Base(BaseModel): "specified in the contentctl.yml file. If this version " "does not match the installed version EXACTLY, " "then an exception will be generated.") - + @model_validator(mode="after") def ensureProperVersionOfContentCtl(self)->Self: ''' Different versions of contentctl may result in builds of the same underlying content