Skip to content

Commit 1b8d6a0

Browse files
committed
further sketching
Signed-off-by: Matthias Büchse <[email protected]>
1 parent 339f092 commit 1b8d6a0

File tree

5 files changed

+122
-21
lines changed

5 files changed

+122
-21
lines changed

Tests/kaas/plugin/interface.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class KubernetesClusterPlugin():
1313
`kubeconfig_filepath`
1414
1515
- Implement `create_cluster` and `delete_cluster` methods
16-
- Create `__init__(self, config_file)` method to handle api specific
16+
- Create `__init__(self, config, basepath)` method to handle api specific
1717
configurations.
1818
1919
Example:
@@ -24,31 +24,28 @@ class KubernetesClusterPlugin():
2424
2525
class PluginX(KubernetesClusterPlugin):
2626
27-
def __init__(self, config_file):
28-
self.config = config_file
27+
def __init__(self, config, basepath):
28+
self.config = config
29+
self.basepath = basepath # find other config files here
2930
30-
def create_cluster(self, cluster_name, version, kubeconfig_filepath):
31-
self.cluster = ClusterAPI(name=cluster_name, image=cluster_image, kubeconfig_filepath)
32-
self.cluster.create(self.config)
31+
def create_cluster(self, kubeconfig_path):
32+
self.cluster = ClusterAPI(name=self.config['name'], kubeconfig_filepath)
33+
self.cluster.create()
3334
3435
def delete_cluster(self, cluster_name):
35-
self.cluster = ClusterAPI(cluster_name)
36+
self.cluster = ClusterAPI(name=self.config['name'])
3637
self.cluster.delete()
3738
..
3839
"""
3940

40-
def create_cluster(self, cluster_name, version, kubeconfig_filepath):
41+
def create_cluster(self, kubeconfig_path: str):
4142
"""
4243
This method is to be called to create a k8s cluster
43-
:param: cluster_name:
44-
:param: version:
45-
:param: kubeconfig_filepath:
4644
"""
4745
raise NotImplementedError
4846

49-
def delete_cluster(self, cluster_name):
47+
def delete_cluster(self):
5048
"""
5149
This method is to be called in order to unprovision a cluster
52-
:param: cluster_name:
5350
"""
5451
raise NotImplementedError
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import base64
2+
import logging
3+
import os
4+
import os.path
5+
from pathlib import Path
6+
7+
from interface import KubernetesClusterPlugin
8+
from jinja2 import Environment
9+
import kubernetes
10+
import yaml
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
TEMPLATE_KEYS = ('cluster', 'clusterstack', 'kubeconfig')
16+
17+
18+
def load_templates(env, basepath, fn_map, keys=TEMPLATE_KEYS):
19+
new_map = {}
20+
for key in keys:
21+
fn = fn_map.get(key)
22+
if fn is None:
23+
new_map[key] = None
24+
continue
25+
with open(os.path.join(basepath, fn), "r") as fileobj:
26+
new_map[key] = env.from_string(fileobj.read())
27+
missing = [key for k, v in new_map.items() if v is None]
28+
if missing:
29+
raise RuntimeError(f'missing templates: {", ".join(missing)}')
30+
return new_map
31+
32+
33+
class PluginClusterStacks(KubernetesClusterPlugin):
34+
"""
35+
Plugin to handle the provisioning of kubernetes cluster for
36+
conformance testing purpose with the use of Kind
37+
"""
38+
def __init__(self, config, basepath='.', cwd='.'):
39+
self.basepath = basepath
40+
self.cwd = cwd
41+
self.config = config
42+
logger.debug(self.config)
43+
self.env = Environment()
44+
self.template_map = load_templates(self.env, self.basepath, self.config['templates'])
45+
self.vars = self.config['vars']
46+
self.vars['name'] = self.config['name']
47+
self.secrets = self.config['secrets']
48+
self.kubeconfig = yaml.load(
49+
self.template_map['kubeconfig'].render(**self.vars, **self.secrets),
50+
Loader=yaml.SafeLoader,
51+
)
52+
self.client_config = kubernetes.client.Configuration()
53+
token = self.kubeconfig['users'][0]['user']['token']
54+
self.client_config.api_key['authorization'] = 'Bearer {}'.format(token)
55+
self.client_config.host = self.kubeconfig['clusters'][0]['cluster']['server']
56+
self.client_config.ssl_ca_cert = os.path.abspath(os.path.join(self.cwd, 'ca.crt'))
57+
with open(self.client_config.ssl_ca_cert, "wb") as fileobj:
58+
fileobj.write(base64.standard_b64decode(self.kubeconfig['clusters'][0]['cluster']['certificate-authority-data'].encode()))
59+
self.namespace = self.kubeconfig['contexts'][0]['context']['namespace']
60+
61+
def auto_vars_syself(self, api_client):
62+
# beware: the following is quite the incantation
63+
prefix = f"v{self.config['kubernetesVersion']}"
64+
api_instance = kubernetes.client.CustomObjectsApi(api_client)
65+
# mimic `kubectl get clusterstackrelease` (it's a bit more involved with the API)
66+
res = api_instance.list_namespaced_custom_object('clusterstack.x-k8s.io', 'v1alpha1', self.namespace, 'clusterstackreleases')
67+
# filter items by readiness and kubernetesVersion, select fields of interest: name, version
68+
items = [
69+
(item['metadata']['name'], item['status']['kubernetesVersion'])
70+
for item in res['items']
71+
if item['status']['ready']
72+
if item['status']['kubernetesVersion'].startswith(prefix)
73+
]
74+
# sort filtered result by patch version
75+
items.sort(key=lambda item: item[1].rsplit('.', 1)[-1])
76+
# select latest
77+
cs_class_name, cs_version = items[-1]
78+
self.vars.setdefault('cs_class_name', cs_class_name)
79+
self.vars.setdefault('cs_version', cs_version)
80+
81+
def create_cluster(self):
82+
with kubernetes.client.ApiClient(self.client_config) as api_client:
83+
if self.config.get('autoVars') == 'syself':
84+
self.auto_vars_syself(api_client)
85+
# write out cluster.yaml for purposes of documentation; we won't use kubectl apply -f
86+
cluster_yaml = self.template_map['cluster'].render(**self.vars)
87+
cluster_dict = yaml.load(cluster_yaml, Loader=yaml.SafeLoader)
88+
with open(os.path.join(self.cwd, 'cluster.yaml'), "w") as fileobj:
89+
fileobj.write(cluster_yaml)
90+
# kubernetes.utils.create_from_dict(api_client, cluster_dict)
91+
api_instance = kubernetes.client.CustomObjectsApi(api_client)
92+
# mimic `kubectl apply -f` (it's a bit more involved with the API)
93+
res = api_instance.create_namespaced_custom_object('cluster.x-k8s.io', 'v1beta1', self.namespace, 'clusters', cluster_dict, field_manager='plugin_clusterstacks')
94+
95+
def delete_cluster(self):
96+
cluster_name = self.config['name']
97+
KindCluster(cluster_name).delete()

Tests/kaas/plugin/plugin_kind.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,20 @@ class PluginKind(KubernetesClusterPlugin):
1414
Plugin to handle the provisioning of kubernetes cluster for
1515
conformance testing purpose with the use of Kind
1616
"""
17-
def __init__(self, config, basepath='.'):
17+
def __init__(self, config, basepath='.', cwd='.'):
1818
self.basepath = basepath
19+
self.cwd = cwd
1920
self.config = config
2021
logger.debug(self.config)
2122

22-
def create_cluster(self, kubeconfig_path):
23+
def create_cluster(self):
2324
cluster_name = self.config['name']
2425
cluster_image = self.config['image']
2526
cluster_yaml = self.config.get('cluster')
2627
if cluster_yaml and not os.path.isabs(cluster_yaml):
2728
cluster_yaml = os.path.normpath(os.path.join(self.basepath, cluster_yaml))
28-
cluster = KindCluster(name=cluster_name, image=cluster_image, kubeconfig=Path(kubeconfig_path))
29+
kubeconfig = Path(os.path.join(self.cwd, 'kubeconfig.yaml'))
30+
cluster = KindCluster(name=cluster_name, image=cluster_image, kubeconfig=kubeconfig)
2931
cluster.create(cluster_yaml)
3032

3133
def delete_cluster(self):

Tests/kaas/plugin/run_plugin.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import click
66
import yaml
77

8+
from plugin_clusterstacks import PluginClusterStacks
89
from plugin_kind import PluginKind
910
from plugin_static import PluginStatic
1011

1112

1213
PLUGIN_LOOKUP = {
14+
"clusterstacks": PluginClusterStacks,
1315
"kind": PluginKind,
1416
"static": PluginStatic,
1517
}
@@ -30,11 +32,12 @@ def load_config(path='clusters.yaml'):
3032
return cfg
3133

3234

33-
def init_plugin(plugin_kind, config):
35+
def init_plugin(plugin_kind, config, cwd='.'):
3436
plugin_maker = PLUGIN_LOOKUP.get(plugin_kind)
3537
if plugin_maker is None:
3638
raise ValueError(f"unknown plugin '{plugin_kind}'")
37-
return plugin_maker(config, basepath=BASEPATH)
39+
os.makedirs(cwd, exist_ok=True)
40+
return plugin_maker(config, basepath=BASEPATH, cwd=cwd)
3841

3942

4043
@click.group()
@@ -49,8 +52,8 @@ def create(cfg, cluster_id):
4952
spec = cfg['clusters'][cluster_id]
5053
config = spec['config']
5154
config['name'] = cluster_id
52-
kubeconfig_path = os.path.abspath(os.path.join(cluster_id, 'kubeconfig.yaml'))
53-
init_plugin(spec['kind'], config).create_cluster(kubeconfig_path)
55+
cwd = os.path.abspath(cluster_id)
56+
init_plugin(spec['kind'], config, cwd).create_cluster()
5457

5558

5659
@cli.command()
@@ -60,7 +63,8 @@ def delete(cfg, cluster_id):
6063
spec = cfg['clusters'][cluster_id]
6164
config = spec['config']
6265
config['name'] = cluster_id
63-
init_plugin(spec['kind'], config).delete_cluster()
66+
cwd = os.path.abspath(cluster_id)
67+
init_plugin(spec['kind'], config, cwd).delete_cluster()
6468

6569

6670
if __name__ == '__main__':

Tests/kaas/requirements.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pytest-kind
22
kubernetes
3+
jinja2
34
junitparser

0 commit comments

Comments
 (0)