Skip to content

Commit c221788

Browse files
committed
Restructure GL flavors parsing and module structure
Signed-off-by: Tobias Wolf <[email protected]>
1 parent ff3c0ad commit c221788

File tree

9 files changed

+183
-132
lines changed

9 files changed

+183
-132
lines changed

src/gardenlinux/__init__.py

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .parser import Parser

src/python_gardenlinux_lib/flavors/__main__.py renamed to src/gardenlinux/flavors/__main__.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@
55
import json
66
import os
77
import sys
8-
import yaml
98

10-
from jsonschema import ValidationError
11-
from .parser import group_by_arch, parse_flavors, validate_flavors
9+
from .parser import Parser
1210

1311

1412
def generate_markdown_table(combinations, no_arch):
@@ -93,16 +91,10 @@ def main():
9391
sys.exit(f"Error: {flavors_file} does not exist.")
9492

9593
# Load and validate the flavors.yaml
96-
with open(flavors_file, 'r') as file:
97-
flavors_data = yaml.safe_load(file)
94+
with open(flavors_file, "r") as file:
95+
flavors_data = file.read()
9896

99-
try:
100-
validate_flavors(flavors_data)
101-
except ValidationError as e:
102-
sys.exit(f"Validation Error: {e.message}")
103-
104-
combinations = parse_flavors(
105-
flavors_data,
97+
combinations = Parser(flavors_data).filter(
10698
include_only_patterns=args.include_only,
10799
wildcard_excludes=args.exclude,
108100
only_build=args.build,
@@ -114,7 +106,7 @@ def main():
114106
)
115107

116108
if args.json_by_arch:
117-
grouped_combinations = group_by_arch(combinations)
109+
grouped_combinations = Parser.group_by_arch(combinations)
118110

119111
# If --no-arch, strip architectures from the grouped output
120112
if args.no_arch:
@@ -128,7 +120,7 @@ def main():
128120
print(generate_markdown_table(combinations, args.no_arch))
129121
else:
130122
if args.no_arch:
131-
printable_combinations = sorted(set(remove_arch(combinations)))
123+
printable_combinations = sorted(set(Parser.remove_arch(combinations)))
132124
else:
133125
printable_combinations = sorted(set(comb[1] for comb in combinations))
134126

src/gardenlinux/flavors/parser.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from jsonschema import validate as jsonschema_validate
2+
import fnmatch
3+
import logging
4+
import yaml
5+
6+
from ..constants import GL_FLAVORS_SCHEMA
7+
8+
class Parser(object):
9+
def __init__(self, data, logger = None):
10+
flavors_data = (yaml.safe_load(data) if isinstance(data, str) else data)
11+
jsonschema_validate(instance=flavors_data, schema=GL_FLAVORS_SCHEMA)
12+
13+
self._flavors_data = flavors_data
14+
self._logger = logger
15+
16+
if self._logger is None:
17+
self._logger = logging.getLogger("gardenlinux.flavors")
18+
19+
if not self._logger.hasHandlers():
20+
self._logger.addHandler(logging.NullHandler())
21+
22+
self._logger.debug("flavors.Parser initialized with data: {0!r}".format(flavors_data))
23+
24+
def filter(
25+
self,
26+
include_only_patterns=[],
27+
wildcard_excludes=[],
28+
only_build=False,
29+
only_test=False,
30+
only_test_platform=False,
31+
only_publish=False,
32+
filter_categories=[],
33+
exclude_categories=[]
34+
):
35+
"""Parses the flavors.yaml file and generates combinations."""
36+
self._logger.debug("flavors.Parser filtering with {0}".format(locals()))
37+
38+
combinations = [] # Use a list for consistent order
39+
40+
for target in self._flavors_data['targets']:
41+
name = target['name']
42+
category = target.get('category', '')
43+
44+
# Apply category filters
45+
if filter_categories and category not in filter_categories:
46+
continue
47+
if exclude_categories and category in exclude_categories:
48+
continue
49+
50+
for flavor in target['flavors']:
51+
features = flavor.get('features', [])
52+
arch = flavor.get('arch', 'amd64')
53+
build = flavor.get('build', False)
54+
test = flavor.get('test', False)
55+
test_platform = flavor.get('test-platform', False)
56+
publish = flavor.get('publish', False)
57+
58+
# Apply flag-specific filters in the order: build, test, test-platform, publish
59+
if only_build and not build:
60+
continue
61+
if only_test and not test:
62+
continue
63+
if only_test_platform and not test_platform:
64+
continue
65+
if only_publish and not publish:
66+
continue
67+
68+
# Process features
69+
formatted_features = f"-{'-'.join(features)}" if features else ""
70+
71+
# Construct the combination
72+
combination = f"{name}-{formatted_features}-{arch}"
73+
74+
# Format the combination to clean up "--" and "-_"
75+
combination = combination.replace("--", "-").replace("-_", "_")
76+
77+
# Exclude combinations explicitly
78+
if Parser.should_exclude(combination, [], wildcard_excludes):
79+
continue
80+
81+
# Apply include-only filters
82+
if not Parser.should_include_only(combination, include_only_patterns):
83+
continue
84+
85+
combinations.append((arch, combination))
86+
87+
return sorted(combinations, key=lambda platform: platform[1].split("-")[0]) # Sort by platform name
88+
89+
@staticmethod
90+
def group_by_arch(combinations):
91+
"""Groups combinations by architecture into a JSON dictionary."""
92+
arch_dict = {}
93+
for arch, combination in combinations:
94+
arch_dict.setdefault(arch, []).append(combination)
95+
for arch in arch_dict:
96+
arch_dict[arch] = sorted(set(arch_dict[arch])) # Deduplicate and sort
97+
return arch_dict
98+
99+
@staticmethod
100+
def remove_arch(combinations):
101+
"""Removes the architecture from combinations."""
102+
return [combination.replace(f"-{arch}", "") for arch, combination in combinations]
103+
104+
@staticmethod
105+
def should_exclude(combination, excludes, wildcard_excludes):
106+
"""
107+
Checks if a combination should be excluded based on exact match or wildcard patterns.
108+
"""
109+
# Exclude if in explicit excludes
110+
if combination in excludes:
111+
return True
112+
# Exclude if matches any wildcard pattern
113+
return any(fnmatch.fnmatch(combination, pattern) for pattern in wildcard_excludes)
114+
115+
@staticmethod
116+
def should_include_only(combination, include_only_patterns):
117+
"""
118+
Checks if a combination should be included based on `--include-only` wildcard patterns.
119+
If no patterns are provided, all combinations are included by default.
120+
"""
121+
if not include_only_patterns:
122+
return True
123+
return any(fnmatch.fnmatch(combination, pattern) for pattern in include_only_patterns)

src/python_gardenlinux_lib/flavors/parse_flavors.py

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
#!/usr/bin/env python
22

3+
# @TODO: This code is provided for backward compatibility only and deprecated.
4+
5+
from gardenlinux.flavors.parser import Parser
36
from git import Git
47
import base64
58
import json
69
import logging
710
import os
11+
import sys
812
import subprocess
913
import yaml
1014

11-
from .parser import (
12-
group_by_arch,
13-
remove_arch,
14-
should_include_only,
15-
should_exclude,
16-
validate_flavors
17-
)
18-
19-
from .parser import parse_flavors as parse_flavors_data
20-
21-
from ..constants import GL_FLAVORS_SCHEMA
15+
from gardenlinux.constants import GL_FLAVORS_SCHEMA
2216

2317
# Define the schema for validation
2418
SCHEMA = GL_FLAVORS_SCHEMA
@@ -29,6 +23,52 @@ def find_repo_root():
2923

3024
return Git(".").rev_parse("--show-superproject-working-tree")
3125

26+
def validate_flavors(data):
27+
"""Validate the flavors.yaml data against the schema."""
28+
try:
29+
validate(instance=data, schema=SCHEMA)
30+
except ValidationError as e:
31+
sys.exit(f"Validation Error: {e.message}")
32+
33+
34+
def should_exclude(combination, excludes, wildcard_excludes):
35+
"""
36+
Checks if a combination should be excluded based on exact match or wildcard patterns.
37+
"""
38+
return Parser.should_exclude(combination, excludes, wildcard_excludes)
39+
40+
41+
def should_include_only(combination, include_only_patterns):
42+
"""
43+
Checks if a combination should be included based on `--include-only` wildcard patterns.
44+
If no patterns are provided, all combinations are included by default.
45+
"""
46+
return Parser.should_include_only(combination, include_only_patterns)
47+
48+
49+
def parse_flavors_data(
50+
data,
51+
include_only_patterns=None,
52+
wildcard_excludes=None,
53+
only_build=False,
54+
only_test=False,
55+
only_test_platform=False,
56+
only_publish=False,
57+
filter_categories=None,
58+
exclude_categories=None,
59+
):
60+
"""Parse flavors.yaml data and generate combinations."""
61+
return Parser(data).filter(
62+
include_only_patterns,
63+
wildcard_excludes,
64+
only_build,
65+
only_test,
66+
only_test_platform,
67+
only_publish,
68+
filter_categories,
69+
exclude_categories
70+
)
71+
3272
def _get_flavors_from_github(commit):
3373
"""Returns the flavors.yaml from GitHub if readable."""
3474

@@ -87,7 +127,7 @@ def parse_flavors_commit(
87127
"""
88128

89129
if logger is None:
90-
logger = logging.getLogger("gardenlinux.lib.flavors")
130+
logger = logging.getLogger("gardenlinux.flavors")
91131
logger.addHandler(logging.NullHandler())
92132

93133
version_info = (

src/python_gardenlinux_lib/flavors/parser.py

Lines changed: 0 additions & 103 deletions
This file was deleted.

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import pytest
99
from dotenv import load_dotenv
1010

11-
GL_ROOT_DIR = "test-data/gardenlinux/"
11+
GL_ROOT_DIR = "test-data/gardenlinux"
1212

1313

1414
def write_zot_config(config_dict, file_path):

tests/test_push_image.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from idlelib.window import registry
2-
31
import pytest
42
import os
53

0 commit comments

Comments
 (0)