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

GitHub Action to Compare Dependencies #29728

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3a43cd2
added gitub action
shmuel44 Jul 16, 2024
c0ae7c1
pipenv
shmuel44 Jul 16, 2024
8290786
pipenv
shmuel44 Jul 16, 2024
69aee8e
pipenv
shmuel44 Jul 16, 2024
2c249f8
pipenv
shmuel44 Jul 16, 2024
debe4a9
remove pipenv shell
shmuel44 Jul 16, 2024
4956cbb
pipenv install
shmuel44 Jul 16, 2024
df88ee6
path
shmuel44 Jul 16, 2024
76fba50
run python
shmuel44 Jul 16, 2024
7faa2e7
pipenv install
shmuel44 Jul 16, 2024
ce78770
remove changed-files
shmuel44 Jul 16, 2024
0b0e2df
error
shmuel44 Jul 16, 2024
a73f7d5
return 1/0
shmuel44 Jul 16, 2024
3ac5966
main
shmuel44 Jul 16, 2024
ce86ffb
no return
shmuel44 Jul 16, 2024
e059f9f
return
shmuel44 Jul 16, 2024
b78da47
print
shmuel44 Jul 16, 2024
9e87de4
fix
shmuel44 Jul 16, 2024
39dd6ac
line number
shmuel44 Jul 16, 2024
942f0d1
cd
shmuel44 Jul 16, 2024
0153814
print
shmuel44 Jul 16, 2024
31a9ec3
line_number
shmuel44 Jul 16, 2024
ed62dfb
str
shmuel44 Jul 16, 2024
36a9ada
Update .github/workflows/sync_native_dependency.yml
shmuel44 Jul 18, 2024
19fbf10
Update .github/workflows/sync_native_dependency.yml
shmuel44 Jul 18, 2024
8286c18
Update utils/compare_dependency_constraints.py
shmuel44 Jul 18, 2024
70f6039
Update utils/compare_dependency_constraints.py
shmuel44 Jul 18, 2024
622511e
CR
shmuel44 Jul 18, 2024
5268bcd
CR
shmuel44 Jul 18, 2024
3b5110e
pull from master
shmuel44 Aug 12, 2024
3d87c1b
compares the dependencies of `python3-ubi` against `py3-tools-ubi`
shmuel44 Aug 12, 2024
1a0111c
Update docker/py3-native/pyproject.toml
shmuel44 Aug 12, 2024
79e9bb8
.get(dependency)
shmuel44 Aug 12, 2024
c5762b2
Merge branch 'sk_github_sync_native' of https://github.com/demisto/do…
shmuel44 Aug 12, 2024
30267da
python3-ubi > py3-tools
shmuel44 Aug 12, 2024
68467b9
reference_image=PY3_TOOLS_IMAGE,
shmuel44 Aug 12, 2024
6b47d36
NATIVE_IMAGE
shmuel44 Aug 12, 2024
9e48b25
ruff
shmuel44 Aug 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/sync_native_dependency.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Compare Dependency Constraints
on:
pull_request:
branches:
- master
jobs:
compare_dependency_constraints_script:
runs-on: ubuntu-latest
if: github.repository == 'demisto/dockerFiles'
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.*"
- name: Install dependencies with pipenv
run: |
python -m pip install --upgrade pip
pip install pipenv
pipenv install
- name: Compare dependency constraints
run: pipenv run python utils/compare_dependency_constraints.py
226 changes: 176 additions & 50 deletions utils/compare_dependency_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,101 +3,226 @@
from typing import Any, NamedTuple
import requests
import toml
import sys

DOCKER_FOLDER = Path(__file__).parent.parent / "docker"
NATIVE_IMAGE = "py3-native"
PY3_TOOLS_UBI_IMAGE = "py3-tools-ubi"
PY3_TOOLS_IMAGE = "py3-tools"
PYPROJECT = "pyproject.toml"
PIPFILE = "Pipfile"


def parse_constraints(dir_name: str) -> dict[str, str]:
class Discrepancy(NamedTuple):
"""Represents a discrepancy between dependencies in different images."""

dependency: str
image: str
reference_image: str
path: Path
in_image: str | None = None
in_reference: str | None = None

def __str__(self) -> str:
return (
f"{self.dependency} is {self.in_image or 'missing'} in {self.image}, "
f"but {self.in_reference or 'missing'} in the {self.reference_image} image. "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
f"but {self.in_reference or 'missing'} in the {self.reference_image} image. "
f"but {self.in_reference or 'missing'} in {self.reference_image}. "

"This discrepancy may cause issues when running content."
)


def get_dependency_file_path(dir_name: str) -> Path:
"""Returns the path to the dependency file (Pipfile or pyproject.toml) in the given directory."""
dir_path = DOCKER_FOLDER / dir_name

if not dir_path.exists():
raise FileNotFoundError(dir_path)
raise FileNotFoundError(f"Directory {dir_path} does not exist.")

pip_path = dir_path / "Pipfile"
pyproject_path = dir_path / "pyproject.toml"
pip_path = dir_path / PIPFILE
pyproject_path = dir_path / PYPROJECT

if pip_path.exists() and pyproject_path.exists():
raise ValueError(
f"Can't have both pyproject and Pipfile in a dockerfile folder ({dir_name})"
f"Can't have both pyproject and Pipfile in a dockerfile folder ({dir_path})"
)

if pip_path.exists():
return lower_dict_keys(_parse_pipfile(pip_path))
return pip_path

if pyproject_path.exists():
return lower_dict_keys(_parse_pyproject(pyproject_path))
return pyproject_path

raise ValueError(f"Neither pyproject nor Pipfile found in {dir_path}")

raise ValueError(f"Neither pyproject nor Pipfile found in {dir_name}")

def parse_constraints(name: str) -> dict[str, str]:
"""Parses the dependency constraints from the given image name."""
path = get_dependency_file_path(name)
if path.suffix == PIPFILE:
return lower_dict_keys(_parse_pipfile(path))

return lower_dict_keys(_parse_pyproject(path))
Comment on lines +60 to +63
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

best to have an

elif : ... 
else: raise RuntimeError

for safety



def _parse_pipfile(path: Path) -> dict[str, str]:
"""Parses the Pipfile and returns the dependencies."""
return toml.load(path).get("packages", {})


def _parse_pyproject(path: Path) -> dict[str, str]:
"""Parses the pyproject.toml file and returns the dependencies."""
return toml.load(path).get("tool", {}).get("poetry", {}).get("dependencies", {})


def lower_dict_keys(dictionary: dict[str, Any]) -> dict[str, Any]:
"""Converts all keys in the dictionary to lowercase."""
return {k.lower(): v for k, v in dictionary.items()}


class Discrepancy(NamedTuple):
dependency: str
image: str
in_image: str | None = None
in_native: str | None = None
def find_library_line_number(lib_name: str, file_path: Path) -> int:
"""
Searches for a library in the pyproject.toml or Pipfile file and returns the line number where it is found.

def __str__(self) -> str:
return f"{self.dependency}: {self.in_image or 'missing'} in {self.image}, {self.in_native or 'missing'} in native"
Parameters:
- lib_name: The name of the library to search for.
- file_path: The directory containing the pyproject.toml or Pipfile.

Returns:
- The line number containing the library name, or 1 if the library is not found.
"""
for line_number, line in enumerate(
file_path.read_text().splitlines(), start=1
): # Start counting from line 1
if lib_name in line:
return line_number

return 1 # default


def compare_constraints(images_contained_in_native: list[str]) -> int:
"""Compares the dependency constraints between different images and reports discrepancies.

This function compares the dependencies of the following images:
- `py3-tools`
- `py3-tools-ubi`
- `native`

against the dependencies of the images listed in `images_contained_in_native`.

Additionally, it compares the dependencies of `py3-tools` against `py3-tools-ubi`.
Comment on lines +102 to +111
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

split the docs between the two methods, this one shouldn't document the others


Args:
images_contained_in_native (list[str]): A list of image names to compare against the native image.

Returns:
int: Returns 1 if there are discrepancies, 0 otherwise.
"""

def compare_constraints(images_contained_in_native: list[str]):
native_constraints = (
parse_constraints("python3-ubi")
| parse_constraints("py3-tools-ubi")
parse_constraints(PY3_TOOLS_IMAGE)
| parse_constraints(PY3_TOOLS_UBI_IMAGE)
| parse_constraints(NATIVE_IMAGE)
)
native_constraint_keys = set(native_constraints.keys())
py3_tools_constraints = parse_constraints(PY3_TOOLS_IMAGE)
py3_tools_ubi_constraints = parse_constraints(PY3_TOOLS_UBI_IMAGE)
discrepancies: list[Discrepancy] = []

for image in images_contained_in_native:
discrepancies: list[Discrepancy] = []

constraints = parse_constraints(image)
constraint_keys = set(constraints.keys())

discrepancies.extend( # image dependencies missing from native
(
Discrepancy(
dependency=dependency,
image=image,
in_image=constraints[dependency],
)
for dependency in sorted(
constraint_keys.difference(native_constraint_keys)
)
discrepancies.extend(compare_with_native(image, native_constraints))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be in compare_with_native


discrepancies.extend(
compare_py3_tools_with_ubi(py3_tools_constraints, py3_tools_ubi_constraints)
)

for discrepancy in discrepancies:
line_number = find_library_line_number(discrepancy.dependency, discrepancy.path)
print(
f"::error file={discrepancy.path},line={line_number},endLine={line_number},title=Native Image Discrepancy::{discrepancy}"
)
return int(bool(discrepancies))


def compare_with_native(image: str, native_constraints: dict) -> list[Discrepancy]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to find_native_discrepancies

path = get_dependency_file_path(image)
constraints = parse_constraints(image)
constraint_keys = set(constraints.keys())
native_constraint_keys = set(native_constraints.keys())

discrepancies: list[Discrepancy] = []

discrepancies.extend( # image dependencies missing from native
(
Discrepancy(
dependency=dependency,
image=image,
reference_image=NATIVE_IMAGE,
in_image=constraints[dependency],
path=path,
)
for dependency in sorted(constraint_keys.difference(native_constraint_keys))
)
)
discrepancies.extend( # shared dependencies with native, different versions
(
Discrepancy(
dependency=dependency,
image=image,
reference_image=NATIVE_IMAGE,
in_image=constraints[dependency],
in_reference=native_constraints[dependency],
path=path,
)
for dependency in sorted(
constraint_keys.intersection(native_constraint_keys)
)
if constraints[dependency] != native_constraints[dependency]
)
discrepancies.extend( # shared dependencies with native, different versions
(
Discrepancy(
dependency=dependency,
image=image,
in_image=constraints[dependency],
in_native=native_constraints[dependency],
)
for dependency in sorted(
constraint_keys.intersection(native_constraint_keys)
)
if constraints[dependency] != native_constraints[dependency]
)

return discrepancies


def compare_py3_tools_with_ubi(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def compare_py3_tools_with_ubi(
def find_ubi_discrepancies(

py3_tools_constraints: dict, py3_tools_ubi_constraints: dict
) -> list[Discrepancy]:
py3_tools_keys = set(py3_tools_constraints.keys())
py3_tools_ubi_keys = set(py3_tools_ubi_constraints.keys())

discrepancies: list[Discrepancy] = []

discrepancies.extend( # py3-tools-ubi dependencies missing from py3-tools
(
Discrepancy(
dependency=dependency,
image=PY3_TOOLS_UBI_IMAGE,
reference_image=PY3_TOOLS_IMAGE,
in_image=py3_tools_ubi_constraints.get(dependency),
in_reference=py3_tools_constraints.get(dependency),
path=get_dependency_file_path(PY3_TOOLS_UBI_IMAGE),
)
for dependency in sorted(py3_tools_ubi_keys.difference(py3_tools_keys))
)
)
discrepancies.extend( # shared dependencies with py3-tools, different versions
(
Discrepancy(
dependency=dependency,
image=PY3_TOOLS_UBI_IMAGE,
reference_image=PY3_TOOLS_IMAGE,
in_image=py3_tools_ubi_constraints.get(dependency),
in_reference=py3_tools_constraints.get(dependency),
path=get_dependency_file_path(PY3_TOOLS_UBI_IMAGE),
)
for dependency in sorted(py3_tools_ubi_keys.intersection(py3_tools_keys))
if py3_tools_ubi_constraints.get(dependency)
!= py3_tools_constraints.get(dependency)
)
)

for discrepancy in discrepancies:
print(str(discrepancy))
return discrepancies


def load_native_image_conf() -> list[str]:
"""Returns the supported docker images by the native image from a remote JSON file."""
return json.loads(
requests.get(
"https://raw.githubusercontent.com/demisto/content/master/Tests/docker_native_image_config.json",
Expand All @@ -106,4 +231,5 @@ def load_native_image_conf() -> list[str]:
)["native_images"]["native:candidate"]["supported_docker_images"]


compare_constraints(load_native_image_conf())
if __name__ == "__main__":
sys.exit(compare_constraints(load_native_image_conf()))
Loading