Skip to content

Commit 4f2f3b3

Browse files
Do not use os.environ.update to update the environment
Signed-off-by: michal.gubricky <[email protected]>
1 parent 23b4cac commit 4f2f3b3

File tree

2 files changed

+97
-92
lines changed

2 files changed

+97
-92
lines changed

Tests/kaas/plugin/plugin-cluster-stacks-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# This is an example of the config for a cluster-stacks plugin filled with a default values
1+
# This is an example of the config for a cluster-stacks plugins
22

33
clouds_yaml_path: "~/.config/openstack/clouds.yaml" # Path to OpenStack clouds.yaml file
44
cs_name: "scs" # Cluster Stack Name

Tests/kaas/plugin/plugin_cluster_stacks.py

Lines changed: 96 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,27 @@
1010

1111
logger = 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

102121
def 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

164155
class 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

Comments
 (0)