Skip to content

Commit e64b16e

Browse files
committed
refactor: make t8s HelmRelease identifiers instance attributes
Signed-off-by: Chris Werner Rau <cwrau@cwrau.info>
1 parent 779dff7 commit e64b16e

2 files changed

Lines changed: 95 additions & 47 deletions

File tree

Tests/kaas/plugin/plugin_t8s.py

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,6 @@
2222
HR_VERSION = "v2"
2323
HR_PLURAL = "helmreleases"
2424

25-
# All of these are mandated by the ValidatingAdmissionPolicy on the management cluster
26-
# and cannot be changed without updating both the VAP and the RBAC.
27-
HR_NAME = "scs-kaas-certification"
28-
HR_NAMESPACE = "scs-kaas-certification"
29-
HR_KUBECONFIG_SECRET = "scs-kaas-certification-kubeconfig"
30-
HR_CHART_REF = {
31-
"kind": "HelmChart",
32-
"name": "scs-kaas-certification",
33-
"namespace": "flux-system",
34-
}
35-
HR_VALUES_FIXED = {
36-
"cloud": "bfe2-prod",
37-
"controlPlane": {"hosted": True},
38-
"metadata": {
39-
"customerID": 1111,
40-
"customerName": "teuto.net Netzdienste GmbH",
41-
"friendlyName": "scs-kaas-certification",
42-
"serviceLevelAgreement": "None",
43-
},
44-
"nodePools": {
45-
"pool-0": {"flavor": "standard.2.1905", "replicas": 2},
46-
},
47-
}
48-
4925

5026
class PluginT8s(KubernetesClusterPlugin):
5127
"""
@@ -62,13 +38,51 @@ class PluginT8s(KubernetesClusterPlugin):
6238
kubeconfig: t8s-kubeconfig.yaml (management cluster kubeconfig template)
6339
secrets:
6440
token: '{{ clouds_conf.teuto_mgmt_token }}'
41+
cloud: 'bfe2-prod' (optional, defaults to 'bfe2-prod')
42+
controlPlaneHosted: true (optional, defaults to true)
43+
nodePools: (optional, defaults to a single 'pool-0')
44+
pool-0:
45+
flavor: 'standard.2.1905'
46+
replicas: 2
47+
metadata: (optional; individual keys default as shown)
48+
customerID: 1111
49+
customerName: 'teuto.net Netzdienste GmbH'
50+
friendlyName: 'scs-kaas-certification'
51+
serviceLevelAgreement: 'None'
6552
"""
6653

6754
def __init__(self, config: dict[str, Any], basepath: str = ".", cwd: str = ".") -> None:
6855
self.basepath = basepath
6956
self.cwd = cwd
7057
self.env = Environment()
7158

59+
# All of these are mandated by the ValidatingAdmissionPolicy on the management cluster
60+
# and cannot be changed without updating both the VAP and the RBAC. They are instance
61+
# (rather than module-level) attributes because, in principle, different certification
62+
# targets could be configured with different values here.
63+
self.hr_name = "scs-kaas-certification"
64+
self.hr_namespace = "scs-kaas-certification"
65+
self.hr_kubeconfig_secret = "scs-kaas-certification-kubeconfig"
66+
self.hr_chart_ref: dict[str, str] = {
67+
"kind": "HelmChart",
68+
"name": "scs-kaas-certification",
69+
"namespace": "flux-system",
70+
}
71+
metadata_config: dict[str, Any] = config.get("metadata", {})
72+
self.hr_values_fixed: dict[str, Any] = {
73+
"cloud": config.get("cloud", "bfe2-prod"),
74+
"controlPlane": {"hosted": config.get("controlPlaneHosted", True)},
75+
"metadata": {
76+
"customerID": metadata_config.get("customerID", 1111),
77+
"customerName": metadata_config.get("customerName", "teuto.net Netzdienste GmbH"),
78+
"friendlyName": metadata_config.get("friendlyName", "scs-kaas-certification"),
79+
"serviceLevelAgreement": metadata_config.get("serviceLevelAgreement", "None"),
80+
},
81+
"nodePools": config.get("nodePools", {
82+
"pool-0": {"flavor": "standard.2.1905", "replicas": 2},
83+
}),
84+
}
85+
7286
fn: str = config["templates"]["kubeconfig"]
7387
with open(os.path.join(basepath, fn), "r") as f:
7488
self.kubeconfig_template: Template = self.env.from_string(f.read())
@@ -91,12 +105,12 @@ def _build_helmrelease(self) -> dict[str, Any]:
91105
return {
92106
"apiVersion": f"{HR_GROUP}/{HR_VERSION}",
93107
"kind": "HelmRelease",
94-
"metadata": {"name": HR_NAME, "namespace": HR_NAMESPACE},
108+
"metadata": {"name": self.hr_name, "namespace": self.hr_namespace},
95109
"spec": {
96-
"chartRef": HR_CHART_REF,
110+
"chartRef": self.hr_chart_ref,
97111
"driftDetection": {"mode": "enabled"},
98112
"interval": "1m",
99-
"values": {**HR_VALUES_FIXED, "version": self.k8s_version},
113+
"values": {**self.hr_values_fixed, "version": self.k8s_version},
100114
},
101115
}
102116

@@ -105,17 +119,17 @@ def _delete_helmrelease(self, co_api: CustomObjectsApi) -> None:
105119
co_api.delete_namespaced_custom_object(
106120
HR_GROUP,
107121
HR_VERSION,
108-
HR_NAMESPACE,
122+
self.hr_namespace,
109123
HR_PLURAL,
110-
HR_NAME,
124+
self.hr_name,
111125
)
112126
except ApiException as e:
113127
if e.status == 404:
114-
logger.debug(f"HelmRelease {HR_NAME} not present, nothing to delete")
128+
logger.debug(f"HelmRelease {self.hr_name} not present, nothing to delete")
115129
return
116130
raise
117131
# Give Flux time to begin deletion before we try to recreate
118-
logger.debug(f"HelmRelease {HR_NAME} deletion requested; waiting 30s")
132+
logger.debug(f"HelmRelease {self.hr_name} deletion requested; waiting 30s")
119133
time.sleep(30)
120134

121135
def _apply_helmrelease(self, api_client: ApiClient) -> None:
@@ -132,9 +146,9 @@ def _apply_helmrelease(self, api_client: ApiClient) -> None:
132146
path_params={
133147
'group': HR_GROUP,
134148
'version': HR_VERSION,
135-
'namespace': HR_NAMESPACE,
149+
'namespace': self.hr_namespace,
136150
'plural': HR_PLURAL,
137-
'name': HR_NAME,
151+
'name': self.hr_name,
138152
},
139153
query_params=[('fieldManager', 'plugin_t8s'), ('force', 'true')],
140154
header_params={
@@ -148,15 +162,15 @@ def _apply_helmrelease(self, api_client: ApiClient) -> None:
148162
auth_settings=['BearerToken'],
149163
_return_http_data_only=True,
150164
)
151-
logger.debug(f"HelmRelease {HR_NAME} applied")
165+
logger.debug(f"HelmRelease {self.hr_name} applied")
152166

153167
def _get_kubeconfig_from_secret(self, core_api: CoreV1Api) -> bytes:
154-
secret = cast(V1Secret, core_api.read_namespaced_secret(HR_KUBECONFIG_SECRET, HR_NAMESPACE))
168+
secret = cast(V1Secret, core_api.read_namespaced_secret(self.hr_kubeconfig_secret, self.hr_namespace))
155169
data: dict[str, str] = secret.data or {}
156170
if "value" in data:
157171
return base64.standard_b64decode(data["value"].encode())
158172
raise RuntimeError(
159-
f"kubeconfig secret {HR_KUBECONFIG_SECRET} is missing key 'value'; has keys: {list(data)}"
173+
f"kubeconfig secret {self.hr_kubeconfig_secret} is missing key 'value'; has keys: {list(data)}"
160174
)
161175

162176
def _wait_for_kubeconfig_secret(self, core_api: CoreV1Api) -> bytes:
@@ -170,10 +184,10 @@ def _wait_for_kubeconfig_secret(self, core_api: CoreV1Api) -> bytes:
170184
timeout = next(timeouts)
171185
if not timeout:
172186
raise RuntimeError(
173-
f"Timeout waiting for kubeconfig secret {HR_KUBECONFIG_SECRET}"
187+
f"Timeout waiting for kubeconfig secret {self.hr_kubeconfig_secret}"
174188
)
175189
logger.debug(
176-
f"waiting {timeout}s for kubeconfig secret {HR_KUBECONFIG_SECRET}"
190+
f"waiting {timeout}s for kubeconfig secret {self.hr_kubeconfig_secret}"
177191
)
178192
time.sleep(timeout)
179193

Tests/kaas/plugin/test_plugin_t8s.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,7 @@
88
from kubernetes.client import ApiException, V1Secret
99

1010
from plugin_t8s import (
11-
HR_CHART_REF,
1211
HR_GROUP,
13-
HR_NAME,
14-
HR_NAMESPACE,
15-
HR_KUBECONFIG_SECRET,
16-
HR_VALUES_FIXED,
1712
HR_VERSION,
1813
PluginT8s,
1914
)
@@ -62,13 +57,13 @@ def test_helmrelease_kind(plugin):
6257

6358
def test_helmrelease_name_namespace(plugin):
6459
hr = plugin._build_helmrelease()
65-
assert hr["metadata"]["name"] == HR_NAME
66-
assert hr["metadata"]["namespace"] == HR_NAMESPACE
60+
assert hr["metadata"]["name"] == plugin.hr_name
61+
assert hr["metadata"]["namespace"] == plugin.hr_namespace
6762

6863

6964
def test_helmrelease_chartref(plugin):
7065
hr = plugin._build_helmrelease()
71-
assert hr["spec"]["chartRef"] == HR_CHART_REF
66+
assert hr["spec"]["chartRef"] == plugin.hr_chart_ref
7267
assert hr["spec"]["chartRef"]["kind"] == "HelmChart"
7368
assert hr["spec"]["chartRef"]["name"] == "scs-kaas-certification"
7469
assert hr["spec"]["chartRef"]["namespace"] == "flux-system"
@@ -135,6 +130,45 @@ def test_helmrelease_single_nodepool(plugin):
135130
assert "pool-0" in nodepools
136131

137132

133+
def _make_plugin(tmp_path, **config_overrides):
134+
tpl = tmp_path / "t8s-kubeconfig.yaml"
135+
tpl.write_text(KUBECONFIG_TEMPLATE)
136+
config = {
137+
"kubernetesVersion": "1.35",
138+
"version_patch": 2,
139+
"templates": {"kubeconfig": tpl.name},
140+
"secrets": {},
141+
**config_overrides,
142+
}
143+
with patch("k8s_helper.setup_client_config"):
144+
return PluginT8s(config, basepath=str(tmp_path), cwd=str(tmp_path))
145+
146+
147+
def test_helmrelease_values_from_config(tmp_path):
148+
plugin = _make_plugin(
149+
tmp_path,
150+
cloud="other-cloud",
151+
controlPlaneHosted=False,
152+
nodePools={"pool-1": {"flavor": "custom.flavor", "replicas": 5}},
153+
metadata={
154+
"customerID": 2222,
155+
"customerName": "Other Customer GmbH",
156+
"friendlyName": "other-friendly-name",
157+
"serviceLevelAgreement": "Gold",
158+
},
159+
)
160+
values = plugin._build_helmrelease()["spec"]["values"]
161+
assert values["cloud"] == "other-cloud"
162+
assert values["controlPlane"]["hosted"] is False
163+
assert values["nodePools"] == {"pool-1": {"flavor": "custom.flavor", "replicas": 5}}
164+
assert values["metadata"] == {
165+
"customerID": 2222,
166+
"customerName": "Other Customer GmbH",
167+
"friendlyName": "other-friendly-name",
168+
"serviceLevelAgreement": "Gold",
169+
}
170+
171+
138172
# --- version parsing ---
139173

140174
@pytest.mark.parametrize("version,version_patch,expected", [
@@ -178,7 +212,7 @@ def test_get_kubeconfig_from_secret(plugin):
178212
core_api = MagicMock()
179213
core_api.read_namespaced_secret.return_value = secret
180214
assert plugin._get_kubeconfig_from_secret(core_api) == raw
181-
core_api.read_namespaced_secret.assert_called_once_with(HR_KUBECONFIG_SECRET, HR_NAMESPACE)
215+
core_api.read_namespaced_secret.assert_called_once_with(plugin.hr_kubeconfig_secret, plugin.hr_namespace)
182216

183217

184218
def test_get_kubeconfig_from_secret_null_data(plugin):

0 commit comments

Comments
 (0)