Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/gardenlinux/features/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-

from .cname import CName
from .parser import Parser

__all__ = ["Parser"]
__all__ = ["CName", "Parser"]
79 changes: 31 additions & 48 deletions src/gardenlinux/features/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from .cname import CName
from .parser import Parser

from functools import reduce
Expand Down Expand Up @@ -54,61 +55,43 @@ def main():
), "Please provide either `--features` or `--cname` argument"

arch = None
cname_base = None
flavor = None
commit_id = None
gardenlinux_root = path.dirname(args.feature_dir)
version = None

if args.cname:
re_match = re.match(
"([a-zA-Z0-9]+([\\_\\-][a-zA-Z0-9]+)*?)(-([a-z0-9]+)(-([a-z0-9.]+)-([a-z0-9]+))*)?$",
args.cname,
)
if args.arch is not None:
arch = args.arch

assert re_match, f"Not a valid GardenLinux canonical name {args.cname}"
if args.version is not None:
version = args.version

if re_match.lastindex == 1:
data_splitted = re_match[1].split("-", 1)
if arch is None or arch == "":
arch = args.default_arch

if version is None or version == "":
version_data = get_version_and_commit_id_from_files(gardenlinux_root)
version = f"{version_data[0]}-{version_data[1]}"

cname_base = data_splitted[0]
if args.cname:
cname = CName(args.cname, arch=arch, version=version)

if len(data_splitted) > 1:
if args.arch is None:
arch = data_splitted[1]
else:
cname_base += "-" + data_splitted[1]
else:
arch = re_match[4]
cname_base = re_match[1]
commit_id = re_match[7]
version = re_match[6]
arch = cname.arch
flavor = cname.flavor
commit_id = cname.commit_id
version = cname.version

input_features = Parser.get_cname_as_feature_set(cname_base)
input_features = Parser.get_cname_as_feature_set(flavor)
else:
input_features = args.features

if args.arch is not None:
arch = args.arch

if args.version is not None:
re_match = re.match("([a-z0-9.]+)(-([a-z0-9]+))?$", args.version)
assert re_match, f"Not a valid version {args.version}"

commit_id = re_match[3]
version = re_match[1]

if arch is None or arch == "" and (args.type in ("cname", "arch")):
assert (
args.default_arch
), "Architecture could not be determined and no default architecture set"
arch = args.default_arch

if not commit_id or not version:
version, commit_id = get_version_and_commit_id_from_files(gardenlinux_root)
raise RuntimeError(
"Architecture could not be determined and no default architecture set"
)

if not version and (args.type in ("cname", "version")):
assert args.default_version, "version not specified and no default version set"
version = args.default_version
if version is None or version == "" and (args.type in ("cname", "version")):
raise RuntimeError("Version not specified and no default version set")

feature_dir_name = path.basename(args.feature_dir)

Expand All @@ -124,7 +107,7 @@ def main():
print(arch)
elif args.type in ("cname_base", "cname", "graph"):
graph = Parser(gardenlinux_root, feature_dir_name).filter(
cname_base, additional_filter_func=additional_filter_func
flavor, additional_filter_func=additional_filter_func
)

sorted_features = Parser.sort_graph_nodes(graph)
Expand All @@ -137,7 +120,7 @@ def main():
if args.type == "cname_base":
print(cname_base)
elif args.type == "cname":
cname = cname_base
cname = flavor

if arch is not None:
cname += f"-{arch}"
Expand All @@ -147,16 +130,16 @@ def main():

print(cname)
elif args.type == "graph":
print(graph_as_mermaid_markup(cname_base, graph))
print(graph_as_mermaid_markup(flavor, graph))
elif args.type == "features":
print(
Parser(gardenlinux_root, feature_dir_name).filter_as_string(
cname_base, additional_filter_func=additional_filter_func
flavor, additional_filter_func=additional_filter_func
)
)
elif args.type in ("flags", "elements", "platforms"):
features_by_type = Parser(gardenlinux_root, feature_dir_name).filter_as_dict(
cname_base, additional_filter_func=additional_filter_func
flavor, additional_filter_func=additional_filter_func
)

if args.type == "platforms":
Expand Down Expand Up @@ -194,15 +177,15 @@ def get_minimal_feature_set(graph):
return set([node for (node, degree) in graph.in_degree() if degree == 0])


def graph_as_mermaid_markup(cname_base, graph):
def graph_as_mermaid_markup(flavor, graph):
"""
Generates a mermaid.js representation of the graph.
This is helpful to identify dependencies between features.

Syntax docs:
https://mermaid.js.org/syntax/flowchart.html?id=flowcharts-basic-syntax
"""
markup = f"---\ntitle: Dependency Graph for Feature {cname_base}\n---\ngraph TD;\n"
markup = f"---\ntitle: Dependency Graph for Feature {flavor}\n---\ngraph TD;\n"
for u, v in graph.edges:
markup += f" {u}-->{v};\n"
return markup
Expand Down
76 changes: 76 additions & 0 deletions src/gardenlinux/features/cname.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-

from typing import Optional
import re


class CName(object):
def __init__(self, cname, arch=None, version=None):
self._arch = None
self._flavor = None
self._commit_id = None
self._version = None

re_match = re.match(
"([a-zA-Z0-9]+([\\_\\-][a-zA-Z0-9]+)*?)(-([a-z0-9]+)(-([a-z0-9.]+)-([a-z0-9]+))*)?$",
cname,
)

assert re_match, f"Not a valid GardenLinux canonical name {cname}"

if re_match.lastindex == 1:
self._flavor = re_match[1]
else:
self._commit_id = re_match[7]
self._flavor = re_match[1]
self._version = re_match[6]

if re_match[2] is None:
self._flavor += re_match[3]
else:
self._arch = re_match[4]

if self._arch is None and arch is not None:
self._arch = arch

if self._version is None and version is not None:
re_match = re.match("([a-z0-9.]+)(-([a-z0-9]+))?$", version)
assert re_match, f"Not a valid version {version}"

self._commit_id = re_match[3]
self._version = re_match[1]

@property
def arch(self) -> Optional[str]:
return self._arch

@property
def cname(self) -> str:
cname = self._flavor

if self._arch is not None:
cname += f"-{self._arch}"

if self._commit_id is not None:
cname += f"-{self.version_and_commit_id}"

return cname

@property
def commit_id(self) -> Optional[str]:
return self._commit_id

@property
def flavor(self) -> str:
return self._flavor

@property
def version(self) -> Optional[str]:
return self._version

@property
def version_and_commit_id(self) -> Optional[str]:
if self._commit_id is None:
return None

return f"{self._version}-{self._commit_id}"
49 changes: 16 additions & 33 deletions src/gardenlinux/features/cname_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
get_version_and_commit_id_from_files,
sort_subset,
)
from .cname import CName
from .parser import Parser


Expand All @@ -33,62 +34,44 @@ def main():
assert re_match, f"Not a valid GardenLinux canonical name {args.cname}"

arch = None
commit_id = None
gardenlinux_root = dirname(args.feature_dir)
version = None

if re_match.lastindex == 1:
data_splitted = re_match[1].split("-", 1)

cname_base = data_splitted[0]

if len(data_splitted) > 1:
if args.arch is None:
arch = data_splitted[1]
else:
cname_base += "-" + data_splitted[1]
else:
arch = re_match[4]
cname_base = re_match[1]
commit_id = re_match[7]
version = re_match[6]
version = args.version

if args.arch is not None:
arch = args.arch

assert arch is not None and arch != "", "Architecture could not be determined"
if args.version is not None:
version = args.version

if not commit_id or not version:
version, commit_id = get_version_and_commit_id_from_files(gardenlinux_root)
if not version:
version_data = get_version_and_commit_id_from_files(gardenlinux_root)
version = f"{version_data[0]}-{version_data[1]}"

if args.version is not None:
re_match = re.match("([a-z0-9.]+)(-([a-z0-9]+))?$", args.version)
assert re_match, f"Not a valid version {args.version}"
cname = CName(args.cname, arch=arch, version=version)

commit_id = re_match[3]
version = re_match[1]
assert cname.arch, "Architecture could not be determined"

feature_dir_name = basename(args.feature_dir)

if gardenlinux_root == "":
gardenlinux_root = "."

graph = Parser(gardenlinux_root, feature_dir_name).filter(cname_base)
graph = Parser(gardenlinux_root, feature_dir_name).filter(cname.flavor)

sorted_features = Parser.sort_graph_nodes(graph)
minimal_feature_set = get_minimal_feature_set(graph)

sorted_minimal_features = sort_subset(minimal_feature_set, sorted_features)

cname = get_cname_base(sorted_minimal_features)
generated_cname = get_cname_base(sorted_minimal_features)

if arch is not None:
cname += f"-{arch}"
if cname.arch is not None:
generated_cname += f"-{cname.arch}"

if commit_id is not None:
cname += f"-{version}-{commit_id}"
if cname.version_and_commit_id is not None:
generated_cname += f"-{cname.version_and_commit_id}"

print(cname)
print(generated_cname)


if __name__ == "__main__":
Expand Down
51 changes: 0 additions & 51 deletions src/gardenlinux/features/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,57 +221,6 @@ def get_cname_as_feature_set(cname):
cname = cname.replace("_", "-_")
return set(cname.split("-"))

@staticmethod
def get_flavor_from_cname(cname: str, get_arch: bool = True) -> str:
"""
Extracts the flavor from a canonical name.

This method parses a Garden Linux canonical name (cname) and extracts
the flavor component, with or without the architecture suffix.

Example canonical names:
- "aws-gardener_prod-amd64"
- "azure-gardener_prod_tpm2_trustedboot-amd64-1312.2-80ffcc87"

The flavor is the platform plus feature string (e.g., "aws-gardener_prod")

Args:
cname (str): Canonical name of an image
get_arch (bool): Whether to include the architecture in the returned flavor
If True: returns "aws-gardener_prod-amd64"
If False: returns "aws-gardener_prod"

Returns:
str: The extracted flavor string, with or without architecture
"""
# Use regex to extract components from the canonical name
# This handles complex cnames with version and commit hash
re_match = re.match(
"([a-zA-Z0-9]+([\\_\\-][a-zA-Z0-9]+)*?)(-([a-z0-9]+)(-([a-z0-9.]+)-([a-z0-9]+))*)?$",
cname,
)

assert re_match, f"Not a valid GardenLinux canonical name {cname}"

if re_match.lastindex == 1:
data_splitted = re_match[1].split("-", 1)

flavor = data_splitted[0]

if len(data_splitted) > 1:
if get_arch is True:
arch = data_splitted[1]
else:
flavor += "-" + data_splitted[1]
else:
arch = re_match[4]
flavor = re_match[1]
# Add architecture if requested
if get_arch and arch:
return f"{flavor}-{arch}"
else:
return flavor

@staticmethod
def _get_filter_set_callable(filter_set, additional_filter_func):
def filter_func(node):
Expand Down
Loading