1010
1111logger = logging .getLogger ("PluginClusterStacks" )
1212
13+ # Default configuration values
14+ DEFAULTS = {
15+ 'cs_name' : 'scs' ,
16+ 'clouds_yaml_path' : '~/.config/openstack/clouds.yaml' ,
17+ 'git_provider' : 'github' ,
18+ 'git_org_name' : 'SovereignCloudStack' ,
19+ 'git_repo_name' : 'cluster-stacks' ,
20+ 'cluster_topology' : 'true' ,
21+ 'exp_cluster_resource_set' : 'true' ,
22+ 'exp_runtime_sdk' : 'true'
23+ }
24+
25+ # Keys needed for environment variables
26+ ENV_KEYS = {'cs_name' , 'cs_version' , 'cs_channel' , 'cs_cloudname' , 'cs_secretname' , 'cs_class_name' ,
27+ 'cs_namespace' , 'cs_pod_cidr' , 'cs_service_cidr' , 'cs_external_id' , 'cs_k8s_patch_version' ,
28+ 'cs_cluster_name' , 'cs_k8s_version' , 'git_provider' , 'git_org_name' , 'git_repo_name' ,
29+ 'cluster_topology' , 'exp_cluster_resource_set' , 'exp_runtime_sdk' }
30+
1331
1432# Helper functions
15- def wait_for_pods (namespaces , timeout = 240 , interval = 15 ):
33+ def wait_for_pods (self , namespaces , timeout = 240 , interval = 15 , kubeconfig = None ):
1634 """
1735 Waits for all pods in specified namespaces to reach the 'Running' state with all containers ready.
1836
@@ -28,10 +46,11 @@ def wait_for_pods(namespaces, timeout=240, interval=15):
2846 for namespace in namespaces :
2947 try :
3048 # Get pod status in the namespace
31- result = subprocess . run (
32- f"kubectl get pods -n { namespace } -o=jsonpath='{{range .items[*]}}{{.metadata.name}} {{.status.phase}} {{range .status.containerStatuses[*]}}{{.ready}} {{end}}{{ \" \\ n \" }}{{end}}'" ,
33- shell = True , capture_output = True , text = True , check = True
49+ command = (
50+ f"kubectl get pods -n { namespace } --kubeconfig { kubeconfig } "
51+ f"-o=jsonpath='{{range .items[*]}}{{.metadata.name}} {{.status.phase}} {{range .status.containerStatuses[*]}}{{.ready}} {{end}}{{ \" \\ n \" }}{{end}}'"
3452 )
53+ result = self ._run_subprocess (command , f"Error fetching pods in { namespace } " , shell = True , capture_output = True , text = True )
3554
3655 if result .returncode == 0 :
3756 pods_status = result .stdout .strip ().splitlines ()
@@ -100,81 +119,54 @@ def load_config(config_path):
100119
101120
102121def setup_environment_variables (self ):
103- # Cluster Stack Parameters
104- self .clouds_yaml_path = self .config .get ('clouds_yaml_path' , '~/.config/openstack/clouds.yaml' )
105- self .cs_k8s_version = self .cluster_version
106- self .cs_name = self .config .get ('cs_name' , 'scs' )
107- self .cs_version = self .config .get ('cs_version' , 'v1' )
108- self .cs_channel = self .config .get ('cs_channel' , 'stable' )
109- self .cs_cloudname = self .config .get ('cs_cloudname' , 'openstack' )
110- self .cs_secretname = self .cs_cloudname
111-
112- # CSP-related variables and additional cluster configuration
113- self .kubeconfig_cs_cluster_filename = f"kubeconfig-{ self .cluster_name } .yaml"
114- self .cs_class_name = f"openstack-{ self .cs_name } -{ str (self .cs_k8s_version ).replace ('.' , '-' )} -{ self .cs_version } "
115- self .cs_namespace = self .config .get ("cs_namespace" , "default" )
116- self .cs_pod_cidr = self .config .get ('cs_pod_cidr' , '192.168.0.0/16' )
117- self .cs_service_cidr = self .config .get ('cs_service_cidr' , '10.96.0.0/12' )
118- self .cs_external_id = self .config .get ('cs_external_id' , 'ebfe5546-f09f-4f42-ab54-094e457d42ec' )
119- self .cs_k8s_patch_version = self .config .get ('cs_k8s_patch_version' , '6' )
120-
121- if not self .clouds_yaml_path :
122- raise ValueError ("CLOUDS_YAML_PATH environment variable not set." )
123-
124- required_env = {
125- 'CLUSTER_TOPOLOGY' : 'true' ,
126- 'EXP_CLUSTER_RESOURCE_SET' : 'true' ,
127- 'EXP_RUNTIME_SDK' : 'true' ,
128- 'CS_NAME' : self .cs_name ,
129- 'CS_K8S_VERSION' : self .cs_k8s_version ,
130- 'CS_VERSION' : self .cs_version ,
131- 'CS_CHANNEL' : self .cs_channel ,
132- 'CS_CLOUDNAME' : self .cs_cloudname ,
133- 'CS_SECRETNAME' : self .cs_secretname ,
134- 'CS_CLASS_NAME' : self .cs_class_name ,
135- 'CS_NAMESPACE' : self .cs_namespace ,
136- 'CS_POD_CIDR' : self .cs_pod_cidr ,
137- 'CS_SERVICE_CIDR' : self .cs_service_cidr ,
138- 'CS_EXTERNAL_ID' : self .cs_external_id ,
139- 'CS_K8S_PATCH_VERSION' : self .cs_k8s_patch_version ,
140- 'CS_CLUSTER_NAME' : self .cluster_name ,
141- }
142- # Update the environment variables
143- os .environ .update ({key : str (value ) for key , value in required_env .items ()})
144-
145-
146- def setup_git_env (self ):
147- # Setup Git environment variables
148- git_provider = self .config .get ('git_provider' , 'github' )
149- git_org_name = self .config .get ('git_org_name' , 'SovereignCloudStack' )
150- git_repo_name = self .config .get ('git_repo_name' , 'cluster-stacks' )
151-
152- os .environ .update ({
153- 'GIT_PROVIDER_B64' : base64 .b64encode (git_provider .encode ()).decode ('utf-8' ),
154- 'GIT_ORG_NAME_B64' : base64 .b64encode (git_org_name .encode ()).decode ('utf-8' ),
155- 'GIT_REPOSITORY_NAME_B64' : base64 .b64encode (git_repo_name .encode ()).decode ('utf-8' )
122+ """
123+ Constructs and returns a dictionary of required environment variables
124+ based on the configuration.
125+ """
126+ # Calculate values that need to be set dynamically
127+ if hasattr (self , 'cluster_version' ):
128+ self .config ['cs_k8s_version' ] = self .cluster_version
129+ self .config ['cs_class_name' ] = (
130+ f"openstack-{ self .config ['cs_name' ]} -{ str (self .config ['cs_k8s_version' ]).replace ('.' , '-' )} -"
131+ f"{ self .config ['cs_version' ]} "
132+ )
133+ self .config ['cs_secretname' ] = self .config ['cs_cloudname' ]
134+ if hasattr (self , 'cluster_name' ):
135+ self .config ['cs_cluster_name' ] = self .cluster_name
136+
137+ # Construct general environment variables
138+ required_env = {key .upper (): value for key , value in self .config .items () if key in ENV_KEYS }
139+
140+ # Encode Git-related environment variables
141+ required_env .update ({
142+ 'GIT_PROVIDER_B64' : base64 .b64encode (self .config ['git_provider' ].encode ()).decode ('utf-8' ),
143+ 'GIT_ORG_NAME_B64' : base64 .b64encode (self .config ['git_org_name' ].encode ()).decode ('utf-8' ),
144+ 'GIT_REPOSITORY_NAME_B64' : base64 .b64encode (self .config ['git_repo_name' ].encode ()).decode ('utf-8' )
156145 })
157146
158147 git_access_token = os .getenv ('GIT_ACCESS_TOKEN' )
159148 if not git_access_token :
160149 raise ValueError ("GIT_ACCESS_TOKEN environment variable not set." )
161150 os .environ ['GIT_ACCESS_TOKEN_B64' ] = base64 .b64encode (git_access_token .encode ()).decode ('utf-8' )
162151
152+ return required_env
153+
163154
164155class PluginClusterStacks (KubernetesClusterPlugin ):
165156 def __init__ (self , config_file = None ):
166157 self .config = load_config (config_file ) if config_file else {}
167158 logger .debug (self .config )
168159 self .working_directory = os .getcwd ()
160+ for key , value in DEFAULTS .items ():
161+ self .config .setdefault (key , value )
162+ self .clouds_yaml_path = os .path .expanduser (self .config .get ('clouds_yaml_path' ))
163+ self .cs_namespace = self .config .get ('cs_namespace' )
169164 logger .debug (f"Working from { self .working_directory } " )
170165
171166 def create_cluster (self , cluster_name = "scs-cluster" , version = None , kubeconfig_filepath = None ):
172167 self .cluster_name = cluster_name
173168 self .cluster_version = version
174-
175- # Setup variables
176- setup_environment_variables (self )
177- setup_git_env (self )
169+ self .kubeconfig_cs_cluster_filename = f"kubeconfig-{ cluster_name } .yaml"
178170
179171 # Create the Kind cluster
180172 self .cluster = KindCluster (name = cluster_name )
@@ -184,52 +176,55 @@ def create_cluster(self, cluster_name="scs-cluster", version=None, kubeconfig_fi
184176 shutil .move (self .kubeconfig , kubeconfig_filepath )
185177 else :
186178 kubeconfig_filepath = str (self .kubeconfig )
187- os .environ ['KUBECONFIG' ] = kubeconfig_filepath
188179
189180 # Initialize clusterctl with OpenStack as the infrastructure provider
190- self ._run_subprocess (["clusterctl" , "init" , "--infrastructure" , "openstack" ], "Error during clusterctl init" )
181+ self ._run_subprocess (
182+ ["sudo" , "-E" , "clusterctl" , "init" , "--infrastructure" , "openstack" ],
183+ "Error during clusterctl init" ,
184+ kubeconfig = kubeconfig_filepath
185+ )
191186
192187 # Wait for all CAPI pods to be ready
193- wait_for_pods (["capi-kubeadm-bootstrap-system" , "capi-kubeadm-control-plane-system" , "capi-system" ])
188+ wait_for_pods (self , ["capi-kubeadm-bootstrap-system" , "capi-kubeadm-control-plane-system" , "capi-system" ], kubeconfig = kubeconfig_filepath )
194189
195190 # Apply infrastructure components
196- self ._apply_yaml_with_envsubst ("cso-infrastructure-components.yaml" , "Error applying CSO infrastructure components" )
197- self ._apply_yaml_with_envsubst ("cspo-infrastructure-components.yaml" , "Error applying CSPO infrastructure components" )
191+ self ._apply_yaml_with_envsubst ("cso-infrastructure-components.yaml" , "Error applying CSO infrastructure components" , kubeconfig = kubeconfig_filepath )
192+ self ._apply_yaml_with_envsubst ("cspo-infrastructure-components.yaml" , "Error applying CSPO infrastructure components" , kubeconfig = kubeconfig_filepath )
198193
199194 # Deploy CSP-helper chart
200195 helm_command = (
201196 f"helm upgrade -i csp-helper-{ self .cs_namespace } -n { self .cs_namespace } "
202197 f"--create-namespace https://github.com/SovereignCloudStack/openstack-csp-helper/releases/latest/download/openstack-csp-helper.tgz "
203198 f"-f { self .clouds_yaml_path } "
204199 )
205- self ._run_subprocess (helm_command , "Error deploying CSP-helper chart" , shell = True )
200+ self ._run_subprocess (helm_command , "Error deploying CSP-helper chart" , shell = True , kubeconfig = kubeconfig_filepath )
206201
207- wait_for_pods (["cso-system" ])
202+ wait_for_pods (self , ["cso-system" ], kubeconfig = kubeconfig_filepath )
208203
209204 # Create Cluster Stack definition and workload cluster
210- self ._apply_yaml_with_envsubst ("clusterstack.yaml" , "Error applying clusterstack.yaml" )
211- self ._apply_yaml_with_envsubst ("cluster.yaml" , "Error applying cluster.yaml" )
205+ self ._apply_yaml_with_envsubst ("clusterstack.yaml" , "Error applying clusterstack.yaml" , kubeconfig = kubeconfig_filepath )
206+ self ._apply_yaml_with_envsubst ("cluster.yaml" , "Error applying cluster.yaml" , kubeconfig = kubeconfig_filepath )
212207
213208 # Get and wait on kubeadmcontrolplane and retrieve workload cluster kubeconfig
214- kcp_name = self ._get_kubeadm_control_plane_name ()
215- self ._wait_kcp_ready (kcp_name )
216- self ._retrieve_kubeconfig ()
209+ kcp_name = self ._get_kubeadm_control_plane_name (kubeconfig = kubeconfig_filepath )
210+ self ._wait_kcp_ready (kcp_name , kubeconfig = kubeconfig_filepath )
211+ self ._retrieve_kubeconfig (kubeconfig = kubeconfig_filepath )
217212
218213 # Wait for workload system pods to be ready
219- print (self .kubeconfig_cs_cluster_filename )
220214 wait_for_workload_pods_ready (kubeconfig_path = self .kubeconfig_cs_cluster_filename )
221215
222216 def delete_cluster (self , cluster_name = None , kubeconfig_filepath = None ):
217+ self .cluster_name = cluster_name
223218 kubeconfig_cs_cluster_filename = f"kubeconfig-{ cluster_name } .yaml"
224219 try :
225220 # Check if the cluster exists
226- check_cluster_command = f"kubectl get cluster { cluster_name } --kubeconfig { kubeconfig_filepath } "
227- result = subprocess . run (check_cluster_command , shell = True , check = True , capture_output = True , text = True )
221+ check_cluster_command = f"kubectl get cluster { cluster_name } "
222+ result = self . _run_subprocess (check_cluster_command , "Failed to get cluster resource" , shell = True , capture_output = True , text = True , kubeconfig = { kubeconfig_filepath } )
228223
229224 # Proceed with deletion only if the cluster exists
230225 if result .returncode == 0 :
231- delete_command = f"kubectl delete cluster { cluster_name } --timeout=600s --kubeconfig { kubeconfig_filepath } "
232- self ._run_subprocess (delete_command , "Timeout while deleting the cluster" , shell = True )
226+ delete_command = f"kubectl delete cluster { cluster_name } --timeout=600s"
227+ self ._run_subprocess (delete_command , "Timeout while deleting the cluster" , shell = True , kubeconfig = kubeconfig_filepath )
233228
234229 except subprocess .CalledProcessError as error :
235230 if "NotFound" in error .stderr :
@@ -247,7 +242,7 @@ def delete_cluster(self, cluster_name=None, kubeconfig_filepath=None):
247242 if os .path .exists (kubeconfig_filepath ):
248243 os .remove (kubeconfig_filepath )
249244
250- def _apply_yaml_with_envsubst (self , yaml_file , error_msg ):
245+ def _apply_yaml_with_envsubst (self , yaml_file , error_msg , kubeconfig = None ):
251246 try :
252247 # Determine if the file is a local path or a URL
253248 if os .path .isfile (yaml_file ):
@@ -261,19 +256,20 @@ def _apply_yaml_with_envsubst(self, yaml_file, error_msg):
261256 else :
262257 raise ValueError (f"Unknown file or URL: { yaml_file } " )
263258
264- self ._run_subprocess (command , error_msg , shell = True )
259+ self ._run_subprocess (command , error_msg , shell = True , kubeconfig = kubeconfig )
265260 except subprocess .CalledProcessError as error :
266261 raise RuntimeError (f"{ error_msg } : { error } " )
267262
268- def _get_kubeadm_control_plane_name (self ):
263+ def _get_kubeadm_control_plane_name (self , kubeconfig = None ):
269264 max_retries = 6
270265 delay_between_retries = 10
271266 for _ in range (max_retries ):
272267 try :
273- kcp_name = subprocess .run (
274- "kubectl get kubeadmcontrolplane -o=jsonpath='{.items[0].metadata.name}'" ,
275- shell = True , check = True , capture_output = True , text = True
268+ kcp_command = (
269+ "kubectl get kubeadmcontrolplane -o=jsonpath='{.items[0].metadata.name}'"
276270 )
271+ kcp_name = self ._run_subprocess (kcp_command , "Error retrieving kcp_name" , shell = True , capture_output = True , text = True , kubeconfig = kubeconfig )
272+ logger .info (kcp_name )
277273 kcp_name_stdout = kcp_name .stdout .strip ()
278274 if kcp_name_stdout :
279275 print (f"KubeadmControlPlane name: { kcp_name_stdout } " )
@@ -285,26 +281,35 @@ def _get_kubeadm_control_plane_name(self):
285281 else :
286282 raise RuntimeError ("Failed to get kubeadmcontrolplane name" )
287283
288- def _wait_kcp_ready (self , kcp_name ):
284+ def _wait_kcp_ready (self , kcp_name , kubeconfig = None ):
289285 try :
290286 self ._run_subprocess (
291287 f"kubectl wait kubeadmcontrolplane/{ kcp_name } --for=condition=Available --timeout=600s" ,
292288 "Error waiting for kubeadmcontrolplane availability" ,
293- shell = True
289+ shell = True ,
290+ kubeconfig = kubeconfig
294291 )
295292 except subprocess .CalledProcessError as error :
296293 raise RuntimeError (f"Error waiting for kubeadmcontrolplane to be ready: { error } " )
297294
298- def _retrieve_kubeconfig (self ):
295+ def _retrieve_kubeconfig (self , kubeconfig = None ):
299296 kubeconfig_command = (
300297 f"clusterctl get kubeconfig { self .cluster_name } > { self .kubeconfig_cs_cluster_filename } "
301298 )
302- self ._run_subprocess (kubeconfig_command , "Error retrieving kubeconfig" , shell = True )
299+ self ._run_subprocess (kubeconfig_command , "Error retrieving kubeconfig" , shell = True , kubeconfig = kubeconfig )
303300
304- def _run_subprocess (self , command , error_msg , shell = False ):
301+ def _run_subprocess (self , command , error_msg , shell = False , capture_output = False , text = False , kubeconfig = None ):
305302 try :
306- subprocess .run (command , shell = shell , check = True )
307- logger .info (f"{ command } executed successfully" )
303+ env = setup_environment_variables (self )
304+ env ['PATH' ] = f'/usr/local/bin:/usr/bin:{ self .working_directory } '
305+ if kubeconfig :
306+ env ['KUBECONFIG' ] = kubeconfig
307+
308+ # Run the subprocess with the custom environment
309+ result = subprocess .run (command , shell = shell , capture_output = capture_output , text = text , check = True , env = env )
310+
311+ return result
312+
308313 except subprocess .CalledProcessError as error :
309314 logger .error (f"{ error_msg } : { error } " )
310315 raise
0 commit comments