Coverage for archdocs/features/kubernetes/parser.py: 100%
31 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-03 00:00 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-03 00:00 +0000
1import dataclasses
2import typing
4from archdocs.features.kubernetes import const, values, values_lookup
7type _KindNames = tuple[str, ...]
8type _ConfigurationPaths = tuple[tuple[values.ValuePath, str, str], ...]
10_NAME_KEY: typing.Final = "name"
11_TRUE_VALUES: typing.Final = frozenset(("true", "yes", "on"))
12_CONFIGURATION_SOURCE_PATHS: typing.Final[_ConfigurationPaths] = (
13 (("configMapRef", _NAME_KEY), const.CONFIG_MAP_KIND, const.ENVIRONMENT_ATTACHMENT),
14 (("configMapKeyRef", _NAME_KEY), const.CONFIG_MAP_KIND, const.ENVIRONMENT_ATTACHMENT),
15 (("existingConfigMap",), const.CONFIG_MAP_KIND, const.ENVIRONMENT_ATTACHMENT),
16 (("secretRef", _NAME_KEY), const.SECRET_KIND, const.ENVIRONMENT_ATTACHMENT),
17 (("secretKeyRef", _NAME_KEY), const.SECRET_KIND, const.ENVIRONMENT_ATTACHMENT),
18 (("existingSecret",), const.SECRET_KIND, const.ENVIRONMENT_ATTACHMENT),
19 (("configMap", _NAME_KEY), const.CONFIG_MAP_KIND, const.VOLUME_ATTACHMENT),
20 (("secret", "secretName"), const.SECRET_KIND, const.VOLUME_ATTACHMENT),
21)
24def _read_toggle(
25 all_values: values.ManifestValues,
26 toggle_path: values.ValuePath,
27 kind_name: str,
28 all_kinds: _KindNames,
29 /,
30) -> bool:
31 toggle_value: typing.Final = values_lookup.read_first_value(all_values, toggle_path)
32 if toggle_value:
33 return toggle_value.lower() in _TRUE_VALUES
34 return kind_name in all_kinds
37def _read_amounts(all_values: values.ManifestValues, /, *resource_keys: str) -> const.ResourceAmounts:
38 return const.ResourceAmounts(
39 requested_amount=values_lookup.read_first_value(
40 all_values,
41 *[("resources", "requests", one_resource_key) for one_resource_key in resource_keys],
42 ),
43 limited_amount=values_lookup.read_first_value(
44 all_values,
45 *[("resources", "limits", one_resource_key) for one_resource_key in resource_keys],
46 ),
47 )
50def _read_traffic(all_values: values.ManifestValues, all_kinds: _KindNames, /) -> const.TrafficFeatures:
51 ingress_enabled: typing.Final = _read_toggle(all_values, ("ingress", "enabled"), const.INGRESS_KIND, all_kinds)
52 return const.TrafficFeatures(
53 ingress_enabled=ingress_enabled,
54 ingress_hosts=values_lookup.read_values(all_values, ("hosts", "host"), ("rules", "host"))
55 if ingress_enabled
56 else (),
57 ingress_tls_enabled=ingress_enabled
58 and values_lookup.has_any_block(all_values, ("ingress", "tls"), ("spec", "tls")),
59 service_type=values_lookup.read_first_value(all_values, ("service", "type"), ("spec", "type")),
60 service_port=values_lookup.read_int_value(all_values, ("service", "port"), ("ports", "port")),
61 )
64def _read_scaling(all_values: values.ManifestValues, all_kinds: _KindNames, /) -> const.ScalingFeatures:
65 plain_scaling: typing.Final = const.ScalingFeatures(
66 workload_kind=next((one_kind for one_kind in all_kinds if one_kind in const.WORKLOAD_KINDS), ""),
67 replica_count=values_lookup.read_int_value(all_values, ("replicaCount",), ("spec", "replicas")),
68 )
69 if not _read_toggle(all_values, ("autoscaling", "enabled"), const.AUTOSCALER_KIND, all_kinds):
70 return plain_scaling
71 return dataclasses.replace(
72 plain_scaling,
73 min_replicas=values_lookup.read_int_value(all_values, ("minReplicas",)),
74 max_replicas=values_lookup.read_int_value(all_values, ("maxReplicas",)),
75 target_cpu_utilization=values_lookup.read_int_value(
76 all_values,
77 ("targetCPUUtilizationPercentage",),
78 ("target", "averageUtilization"),
79 ),
80 )
83def _read_resources(all_values: values.ManifestValues, all_kinds: _KindNames, /) -> const.ResourceFeatures:
84 return const.ResourceFeatures(
85 cpu_amounts=_read_amounts(all_values, "cpu"),
86 memory_amounts=_read_amounts(all_values, "memory"),
87 gpu_amounts=_read_amounts(all_values, *const.GPU_RESOURCE_KEYS),
88 persistence_enabled=_read_toggle(all_values, ("persistence", "enabled"), const.VOLUME_CLAIM_KIND, all_kinds)
89 or values_lookup.has_any_block(all_values, ("volumeClaimTemplates",)),
90 persistence_size=values_lookup.read_first_value(all_values, ("persistence", "size"), ("requests", "storage")),
91 )
94def _read_configuration(all_values: values.ManifestValues, /) -> tuple[const.ConfigurationSource, ...]:
95 return tuple(
96 dict.fromkeys(
97 const.ConfigurationSource(
98 source_kind=one_source_kind,
99 source_name=one_source_name,
100 attachment_kind=one_attachment_kind,
101 )
102 for one_value_path, one_source_kind, one_attachment_kind in _CONFIGURATION_SOURCE_PATHS
103 for one_source_name in values_lookup.read_values(all_values, one_value_path)
104 ),
105 )
108def find_kubernetes_features(raw_source: str) -> const.KubernetesFeatures:
109 all_values: typing.Final = values.read_manifest_values(raw_source)
110 all_kinds: typing.Final = values_lookup.read_values(all_values, ("kind",))
111 return const.KubernetesFeatures(
112 traffic_features=_read_traffic(all_values, all_kinds),
113 scaling_features=_read_scaling(all_values, all_kinds),
114 resource_features=_read_resources(all_values, all_kinds),
115 configuration_sources=_read_configuration(all_values),
116 )