Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add contentctl_library_version contentctl.yml field #336

Open
wants to merge 10 commits into
base: contentctl_5
Choose a base branch
from
2 changes: 1 addition & 1 deletion .github/workflows/test_against_escu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions contentctl/contentctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from contentctl.input.yml_reader import YmlReader
from contentctl.actions.deploy_acs import Deploy
from contentctl.actions.release_notes import ReleaseNotes

import importlib.metadata
# def print_ascii_art():
# print(
# """
Expand Down Expand Up @@ -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")

Expand All @@ -148,7 +152,6 @@ 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()
config_obj = t.model_dump()

Expand Down Expand Up @@ -192,7 +195,6 @@ def main():
with warnings.catch_warnings(action="ignore"):
config = tyro.cli(models)


if type(config) == init:
t.__dict__.update(config.__dict__)
init_func(t)
Expand Down
58 changes: 53 additions & 5 deletions contentctl/objects/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
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
import re
from pydantic import (
BaseModel, Field, field_validator,
field_serializer, ConfigDict, DirectoryPath,
Expand Down Expand Up @@ -136,7 +137,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
Expand Down Expand Up @@ -165,6 +166,49 @@ 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(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 "
"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
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 = importlib.metadata.version('contentctl')

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 contentctl_library_version defined in contentctl.yml: [{self.contentctl_library_version}]"
"\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/ //')"'''
)



@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:
Expand All @@ -178,7 +222,7 @@ 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).")


class validate(Config_Base):
model_config = ConfigDict(validate_default=True, arbitrary_types_allowed=True)
Expand Down Expand Up @@ -244,6 +288,7 @@ 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]):
Expand Down Expand Up @@ -279,7 +324,10 @@ def getAPIPath(self)->pathlib.Path:
return self.getBuildDir() / "api"

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):
Expand Down
1 change: 1 addition & 0 deletions contentctl/output/yml_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
yaml.representer.SafeRepresenter.represent_int
)


class YmlWriter:

@staticmethod
Expand Down
Loading