diff --git a/VERSIONS-CONFIG.md b/VERSIONS-CONFIG.md index 4112e7e6add..ce778b00189 100644 --- a/VERSIONS-CONFIG.md +++ b/VERSIONS-CONFIG.md @@ -2,6 +2,7 @@ ## Properties +- **`additional-packages`** _(object)_: The additional packages to be added to the versions. - **`external-packages`** _(array)_ - **Items** _(object)_: Cannot contain additional properties. diff --git a/github_app_geo_project/module/utils.py b/github_app_geo_project/module/utils.py index 8af5750ede4..5fd540c1fda 100644 --- a/github_app_geo_project/module/utils.py +++ b/github_app_geo_project/module/utils.py @@ -619,7 +619,6 @@ def git_clone(github_project: configuration.GithubProject, branch: str) -> bool: def get_stabilization_branch(security: c2cciutils.security.Security) -> list[str]: """Get the stabilization versions.""" - alternate_index = security.headers.index("Alternate Tag") if "Alternate Tag" in security.headers else -1 version_index = security.headers.index("Version") if "Version" in security.headers else -1 supported_until_index = ( security.headers.index("Supported Until") if "Supported Until" in security.headers else -1 @@ -632,23 +631,33 @@ def get_stabilization_branch(security: c2cciutils.security.Security) -> list[str _LOGGER.warning("No Supported Until column in the SECURITY.md") return [] - alternate = [] - if alternate_index >= 0: - for row in security.data: - if row[alternate_index]: - alternate.append(row[alternate_index]) - versions = [] for row in security.data: if row[supported_until_index] != "Unsupported": - if alternate: - if row[alternate_index] not in alternate: - versions.append(row[version_index]) - else: - versions.append(row[version_index]) + versions.append(row[version_index]) return versions +def get_alternate_versions(security: c2cciutils.security.Security, branch: str) -> list[str]: + """Get the stabilization versions.""" + alternate_index = security.headers.index("Alternate Tag") if "Alternate Tag" in security.headers else -1 + version_index = security.headers.index("Version") if "Version" in security.headers else -1 + + if alternate_index < 0: + return [] + + if version_index < 0: + _LOGGER.warning("No Version column in the SECURITY.md") + return [] + + for row in security.data: + if row[version_index] == branch: + return [v.strip() for v in row[alternate_index].split(",") if v.strip()] + + _LOGGER.warning("Branch %s not found in the SECURITY.md", branch) + return [] + + def manage_updated(status: dict[str, Any], key: str, days_old: int = 2) -> None: """ Manage the updated status. diff --git a/github_app_geo_project/module/versions/__init__.py b/github_app_geo_project/module/versions/__init__.py index dc3409716cb..3d7ddd6c46a 100644 --- a/github_app_geo_project/module/versions/__init__.py +++ b/github_app_geo_project/module/versions/__init__.py @@ -94,6 +94,7 @@ class _DependenciesBranches(BaseModel): class _EventData(BaseModel): step: int branch: str | None = None + alternate_versions: list[str] | None = None class VersionException(Exception): @@ -159,6 +160,7 @@ async def process( key = f"{context.github_project.owner}/{context.github_project.repository}" status = context.transversal_status.repositories.setdefault(key, _TransversalStatusRepo()) if context.module_event_data.step == 1: + _apply_additional_packages(context) status.url = ( f"https://github.com/{context.github_project.owner}/{context.github_project.repository}" ) @@ -196,11 +198,11 @@ async def process( else: _LOGGER.debug("No SECURITY.md file in the repository, apply on default branch") - stabilization_branch = [repo.default_branch] - status.versions.setdefault( - repo.default_branch, - _TransversalStatusVersion(support="Best effort"), - ).support = "Best effort" + stabilization_branch.append(repo.default_branch) + status.versions.setdefault( + repo.default_branch, + _TransversalStatusVersion(support="Best effort"), + ).support = "Best effort" _LOGGER.debug("Versions: %s", ", ".join(stabilization_branch)) versions = status.versions @@ -210,7 +212,16 @@ async def process( actions = [] for branch in stabilization_branch: - actions.append(module.Action(data=_EventData(step=2, branch=branch), title=branch)) + actions.append( + module.Action( + data=_EventData( + step=2, + branch=branch, + alternate_versions=module_utils.get_alternate_versions(security, branch), + ), + title=branch, + ) + ) return ProcessOutput(actions=actions, transversal_status=context.transversal_status) if context.module_event_data.step == 2: assert context.module_event_data.branch is not None @@ -222,6 +233,10 @@ async def process( raise VersionException("Failed to clone the repository") version_status = status.versions[context.module_event_data.branch] + for alternate in context.module_event_data.alternate_versions or []: + status.versions[alternate] = version_status + for datasource_data in version_status.names_by_datasource.values(): + datasource_data.names = list(set(datasource_data.names)) transversal_status = context.transversal_status _get_names(context, version_status.names_by_datasource, context.module_event_data.branch) @@ -430,32 +445,30 @@ def _get_names( if match.group(1) not in names: names.append(match.group(1)) - if os.path.exists("ci/config.yaml"): - with open("ci/config.yaml", encoding="utf-8") as file: - data = yaml.load(file, Loader=yaml.SafeLoader) - docker_config = data.get("publish", {}).get("docker", {}) - if docker_config: - names = names_by_datasource.setdefault("docker", _TransversalStatusNameByDatasource()).names - for conf in docker_config.get("images", []): - for tag in conf.get("tags", ["{version}"]): - for repository_conf in docker_config.get( - "repository", c2cciutils.configuration.DOCKER_REPOSITORY_DEFAULT - ).values(): - repository_server = repository_conf.get("server", False) - add_names = [] - if repository_server: - add_names.append( - f"{repository_server}/{conf.get('name')}:{tag.format(version=branch)}" - ) + data = c2cciutils.get_config() + docker_config = data.get("publish", {}).get("docker", {}) + if docker_config: + names = names_by_datasource.setdefault("docker", _TransversalStatusNameByDatasource()).names + for conf in docker_config.get("images", []): + for tag in conf.get("tags", ["{version}"]): + for repository_conf in docker_config.get( + "repository", c2cciutils.configuration.DOCKER_REPOSITORY_DEFAULT + ).values(): + repository_server = repository_conf.get("server", False) + add_names = [] + if repository_server: + add_names.append( + f"{repository_server}/{conf.get('name')}:{tag.format(version=branch)}" + ) - else: - add_names = [ - f"{conf.get('name')}:{tag.format(version=branch)}", - f"docker.io/{conf.get('name')}:{tag.format(version=branch)}", - ] - for add_name in add_names: - if add_name not in names: - names.append(add_name) + else: + add_names = [ + f"{conf.get('name')}:{tag.format(version=branch)}", + f"docker.io/{conf.get('name')}:{tag.format(version=branch)}", + ] + for add_name in add_names: + if add_name not in names: + names.append(add_name) for filename in subprocess.run( # nosec ["git", "ls-files", "package.json", "*/package.json"], @@ -695,15 +708,17 @@ def _build_internal_dependencies( ) for datasource_name, dependencies_data in version_data.dependencies_by_datasource.items(): for dependency_name, dependency_versions in dependencies_data.versions_by_names.items(): + if datasource_name not in names.by_datasources: + continue for dependency_version in dependency_versions.versions: - if datasource_name not in names.by_datasources: - continue dependency_data = names.by_datasources[datasource_name] if datasource_name == "docker": - dependency_name = f"{dependency_name}:{dependency_version}" - if dependency_name not in dependency_data.by_package: + full_dependency_name = f"{dependency_name}:{dependency_version}" + else: + full_dependency_name = dependency_name + if full_dependency_name not in dependency_data.by_package: continue - dependency_package_data = dependency_data.by_package[dependency_name] + dependency_package_data = dependency_data.by_package[full_dependency_name] dependency_minor = _canonical_minor_version(datasource_name, dependency_version) support = dependency_package_data.status_by_version.get( dependency_minor, @@ -790,3 +805,11 @@ def _build_reverse_dependency( repo=other_repo, ) ) + + +def _apply_additional_packages( + context: module.ProcessContext[configuration.VersionsConfiguration, _EventData, _TransversalStatus], +) -> None: + for repo, data in context.module_config.get("additional-packages", {}).items(): + pydentic_data = _TransversalStatusRepo(**data) + context.transversal_status.repositories[repo] = pydentic_data diff --git a/github_app_geo_project/module/versions/configuration.py b/github_app_geo_project/module/versions/configuration.py index 4c1970d8a68..5d352775328 100644 --- a/github_app_geo_project/module/versions/configuration.py +++ b/github_app_geo_project/module/versions/configuration.py @@ -2,7 +2,7 @@ Automatically generated file from a JSON schema. """ -from typing import TypedDict +from typing import Any, TypedDict from typing_extensions import Required @@ -10,6 +10,8 @@ VersionsConfiguration = TypedDict( "VersionsConfiguration", { + # The additional packages to be added to the versions + "additional-packages": dict[str, Any], # examples: # - datasource: pypi # package: python diff --git a/github_app_geo_project/module/versions/schema.json b/github_app_geo_project/module/versions/schema.json index 36c5cfdface..c5e3f1fd315 100644 --- a/github_app_geo_project/module/versions/schema.json +++ b/github_app_geo_project/module/versions/schema.json @@ -6,6 +6,10 @@ "additionalProperties": false, "properties": { + "additional-packages": { + "type": "object", + "description": "The additional packages to be added to the versions" + }, "external-packages": { "type": "array", "examples": [ diff --git a/tests/test_module_versions.py b/tests/test_module_versions.py index 5e677de1977..afb99442878 100644 --- a/tests/test_module_versions.py +++ b/tests/test_module_versions.py @@ -259,7 +259,7 @@ def test_get_transversal_dashboard_repo_forward_docker() -> None: support="Best effort", forward=[ _Dependency( - name="camptocamp/other:2.0", + name="camptocamp/other", datasource="docker", version="2.0", support="Best effort", @@ -273,6 +273,73 @@ def test_get_transversal_dashboard_repo_forward_docker() -> None: ) +def test_get_transversal_dashboard_repo_forward_docker_double() -> None: + versions = Versions() + context = Mock() + context.status = _TransversalStatus( + repositories={ + "camptocamp/test": _TransversalStatusRepo( + versions={ + "1.0": _TransversalStatusVersion( + support="Best effort", + dependencies_by_datasource={ + "docker": _TransversalStatusNameInDatasource( + versions_by_names={ + "camptocamp/other": _TransversalStatusVersions(versions=["1.0", "2.0"]), + } + ) + }, + ) + }, + ), + "camptocamp/other": _TransversalStatusRepo( + versions={ + "1.0": _TransversalStatusVersion( + support="Best effort", + names_by_datasource={ + "docker": _TransversalStatusNameByDatasource(names=["camptocamp/other:1.0"]) + }, + ), + "2.0": _TransversalStatusVersion( + support="Best effort", + names_by_datasource={ + "docker": _TransversalStatusNameByDatasource(names=["camptocamp/other:2.0"]) + }, + ), + }, + ), + } + ) + context.params = {"repository": "camptocamp/test"} + output = versions.get_transversal_dashboard(context) + assert output.data["dependencies_branches"] == _DependenciesBranches( + by_branch={ + "1.0": _Dependencies( + support="Best effort", + forward=[ + _Dependency( + name="camptocamp/other", + datasource="docker", + version="1.0", + support="Best effort", + color="--bs-body-bg", + repo="camptocamp/other", + ), + _Dependency( + name="camptocamp/other", + datasource="docker", + version="2.0", + support="Best effort", + color="--bs-body-bg", + repo="camptocamp/other", + ), + ], + reverse=[], + ) + } + ) + + def test_get_transversal_dashboard_repo_forward_inexisting() -> None: versions = Versions() context = Mock()