44import os .path
55from pathlib import Path
66
7- from interface import KubernetesClusterPlugin
87from jinja2 import Environment
98import kubernetes
9+ from kubernetes .client .exceptions import ApiException
1010import yaml
1111
12+ from interface import KubernetesClusterPlugin
13+
1214logger = logging .getLogger (__name__ )
1315
1416
1517TEMPLATE_KEYS = ('cluster' , 'clusterstack' , 'kubeconfig' )
1618
1719
20+ def _setup_client_config (client_config , kubeconfig , cwd = '.' ):
21+ """transfer authentication data from kubeconfig to client_config, creating file `ca.crt`s"""
22+ token = kubeconfig ['users' ][0 ]['user' ]['token' ]
23+ client_config .api_key ['authorization' ] = 'Bearer {}' .format (token )
24+ client_config .host = kubeconfig ['clusters' ][0 ]['cluster' ]['server' ]
25+ client_config .ssl_ca_cert = os .path .abspath (os .path .join (cwd , 'ca.crt' ))
26+ with open (client_config .ssl_ca_cert , "wb" ) as fileobj :
27+ fileobj .write (base64 .standard_b64decode (
28+ kubeconfig ['clusters' ][0 ]['cluster' ]['certificate-authority-data' ].encode ()
29+ ))
30+
31+
32+ def _kubectl_apply_cr (api_client , namespace , resource_dict ):
33+ """mimic `kubectl apply` with a custom resource"""
34+ api_instance = kubernetes .client .CustomObjectsApi (api_client )
35+ group , ver = resource_dict ['apiVersion' ].split ('/' , 1 )
36+ plural = resource_dict ['kind' ].lower () + 's'
37+ return api_instance .create_namespaced_custom_object (
38+ group , ver , namespace , plural , resource_dict , field_manager = 'plugin_clusterstacks' ,
39+ )
40+
41+
1842def load_templates (env , basepath , fn_map , keys = TEMPLATE_KEYS ):
1943 new_map = {}
2044 for key in keys :
@@ -45,19 +69,14 @@ def __init__(self, config, basepath='.', cwd='.'):
4569 self .vars = self .config ['vars' ]
4670 self .vars ['name' ] = self .config ['name' ]
4771 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- )
72+ self .kubeconfig = yaml .load (self ._render_template ('kubeconfig' ), Loader = yaml .SafeLoader )
5273 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 ()))
74+ _setup_client_config (self .client_config , self .kubeconfig , cwd = self .cwd )
5975 self .namespace = self .kubeconfig ['contexts' ][0 ]['context' ]['namespace' ]
6076
77+ def _render_template (self , key ):
78+ return self .template_map [key ].render (** self .vars , ** self .secrets )
79+
6180 def auto_vars_syself (self , api_client ):
6281 # beware: the following is quite the incantation
6382 prefix = f"v{ self .config ['kubernetesVersion' ]} "
@@ -82,15 +101,38 @@ def create_cluster(self):
82101 with kubernetes .client .ApiClient (self .client_config ) as api_client :
83102 if self .config .get ('autoVars' ) == 'syself' :
84103 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 )
104+ # write out cluster.yaml for purposes of documentation
105+ # we will however use the dict instead of calling the shell with `kubectl apply -f`
106+ cluster_yaml = self ._render_template ('cluster' )
87107 cluster_dict = yaml .load (cluster_yaml , Loader = yaml .SafeLoader )
88108 with open (os .path .join (self .cwd , 'cluster.yaml' ), "w" ) as fileobj :
89109 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' )
110+ try :
111+ _kubectl_apply_cr (api_client , self .namespace , cluster_dict )
112+ except ApiException as e :
113+ # 409 means that the object already exists; don't treat that as error
114+ if e .status != 409 :
115+ raise
116+ name = self .config ['name' ]
117+ secret_name = f'{ name } -kubeconfig'
118+ api_instance = kubernetes .client .CustomObjectsApi (api_client )
119+ while True :
120+ # mimic `kubectl get machines` (it's a bit more involved with the API)
121+ res = api_instance .list_namespaced_custom_object ('cluster.x-k8s.io' , 'v1beta1' , self .namespace , 'machines' )
122+ items = [
123+ (item ['metadata' ]['name' ], item ['status' ]['phase' ].lower ())
124+ for item in res ['items' ]
125+ if item ['spec' ]['clusterName' ] == name
126+ ]
127+ working = [item [0 ] for item in items if item [1 ] != 'provisioned' ]
128+ if not working :
129+ break
130+ logger .debug ('waiting 30 s for machines to become ready:' , items )
131+ time .sleep (30 )
132+ # mimic `kubectl get secrets NAME -o=jsonpath='{.data.value}' | base64 -d > kubeconfig.yaml`
133+ res = kubernetes .client .CoreV1Api (api_client ).read_namespaced_secret (secret_name , self .namespace )
134+ with open (os .path .join (self .cwd , 'kubeconfig.yaml' ), 'wb' ) as fileobj :
135+ fileobj .write (base64 .standard_b64decode (res .data ['value' ].encode ()))
94136
95137 def delete_cluster (self ):
96138 cluster_name = self .config ['name' ]
0 commit comments