diff --git a/frontend/packages/vsphere-plugin/locales/en/vsphere-plugin.json b/frontend/packages/vsphere-plugin/locales/en/vsphere-plugin.json
index 780b7fbec1e..ce6de2dd549 100644
--- a/frontend/packages/vsphere-plugin/locales/en/vsphere-plugin.json
+++ b/frontend/packages/vsphere-plugin/locales/en/vsphere-plugin.json
@@ -11,7 +11,7 @@
"Failing {{reason}}": "Failing {{reason}}",
"Failed to load kubecontrollermanager": "Failed to load kubecontrollermanager",
"Failed to parse cloud provider config {{cm}}": "Failed to parse cloud provider config {{cm}}",
- "Unknown format": "Unknown format",
+ "The following content was expected to be defined in the configMap: {{ expectedValues }}": "The following content was expected to be defined in the configMap: {{ expectedValues }}",
"Failed to persist {{secret}}": "Failed to persist {{secret}}",
"Failed to patch kubecontrollermanager": "Failed to patch kubecontrollermanager",
"Failed to patch cloud provider config": "Failed to patch cloud provider config",
@@ -34,6 +34,8 @@
"Warning: Updating this value will break any existing PersistentVolumes.": "Warning: Updating this value will break any existing PersistentVolumes.",
"Virtual Machine Folder": "Virtual Machine Folder",
"Provide <1>datacenter1> folder which contains VMs of the cluster.": "Provide <1>datacenter1> folder which contains VMs of the cluster.",
+ "Primary network": "Primary network",
+ "Provide the <1>primary network1> of the cluster.": "Provide the <1>primary network1> of the cluster.",
"Saving": "Saving",
"Save configuration": "Save configuration",
"Cancel": "Cancel",
diff --git a/frontend/packages/vsphere-plugin/src/components/VSphereConnectionForm.tsx b/frontend/packages/vsphere-plugin/src/components/VSphereConnectionForm.tsx
index a55a596db2e..5e0bd8c0aaa 100644
--- a/frontend/packages/vsphere-plugin/src/components/VSphereConnectionForm.tsx
+++ b/frontend/packages/vsphere-plugin/src/components/VSphereConnectionForm.tsx
@@ -146,6 +146,21 @@ export const VSphereConnectionForm = () => {
>
+
+ Provide the primary network of the cluster.
+
+ }
+ />
+ }
+ fieldId="connection-network"
+ >
+
+
);
};
diff --git a/frontend/packages/vsphere-plugin/src/components/VSphereConnectionModal.tsx b/frontend/packages/vsphere-plugin/src/components/VSphereConnectionModal.tsx
index e1e13937598..8148af25f3c 100644
--- a/frontend/packages/vsphere-plugin/src/components/VSphereConnectionModal.tsx
+++ b/frontend/packages/vsphere-plugin/src/components/VSphereConnectionModal.tsx
@@ -108,6 +108,7 @@ const validationSchema = Yup.lazy((values: ConnectionFormFormikValues) =>
username: Yup.string().required('Username is required.'),
password: Yup.string().required('Password is required.'),
datacenter: Yup.string().required('Datacenter is required.'),
+ network: Yup.string(),
defaultDatastore: Yup.string()
.required('Default data store is required.')
.test(
diff --git a/frontend/packages/vsphere-plugin/src/components/persist.ts b/frontend/packages/vsphere-plugin/src/components/persist.ts
index 799a7589683..965835b76b0 100644
--- a/frontend/packages/vsphere-plugin/src/components/persist.ts
+++ b/frontend/packages/vsphere-plugin/src/components/persist.ts
@@ -160,12 +160,92 @@ const updateYamlFormat = (
return dump(cmCfg);
};
-const findAndReplace = (str: string, init: string, replacement: string): string | undefined => {
- if (!str || !str.includes(init)) {
- return undefined;
+type UpdateConfigMapResult = {
+ config: string;
+ expectedValues: string[];
+};
+
+const getUpdatedConfig = (
+ result: UpdateConfigMapResult,
+ init: string,
+ replacement: string,
+): UpdateConfigMapResult | undefined => {
+ const cfg = result.config;
+ if (!cfg || !cfg.includes(init)) {
+ result.expectedValues.push(init);
+ return result;
}
- return str.replace(init, replacement);
+ return {
+ config: cfg.replace(init, replacement),
+ expectedValues: result.expectedValues,
+ };
+};
+
+// Updates the configMap folder value if the following conditions are met:
+// 1 - The ConfigMap includes the entry for the "folder"
+// 2 - The infrastructure CRD either has no "folder" entry, or it has a "folder" entry that matches the "folder" entry in the ConfigMap
+const getUpdatedConfigMapFolder = (
+ result: UpdateConfigMapResult,
+ initFolder: string,
+ newFolder: string,
+): UpdateConfigMapResult | undefined => {
+ const cfg = result.config;
+ const folderLineMatch = cfg.match(/folder\s*=\s*["']?([^"'\n\r]+)["']?/);
+ if (folderLineMatch) {
+ const folderLine = folderLineMatch[0];
+ const folderValue = folderLineMatch[1].trim();
+
+ if (!initFolder || initFolder === folderValue) {
+ return {
+ config: cfg.replace(folderLine, `folder = "${newFolder}"`),
+ expectedValues: result.expectedValues,
+ };
+ }
+ }
+ return getUpdatedConfig(result, `folder = "${initFolder}"`, `folder = "${newFolder}"`);
+};
+
+// Updates the configMap resourcepool-path value if the following conditions are met:
+// 1 - The ConfigMap includes the entry for the "resourcepool-path"
+// 2 - The existing value for "resourcepool-path" in the ConfigMap starts with the pattern "/${datacenter}/host/${vCenterCluster}/Resources"
+// Additionally, the resourcepool-path may contain additional path segments after "/Resources", which will be preserved.
+const getUpdatedConfigMapResourcePool = (
+ result: UpdateConfigMapResult,
+ initDatacenter: string,
+ initVCenterCluster: string,
+ datacenter: string,
+ vCenterCluster: string,
+): UpdateConfigMapResult | undefined => {
+ const cfg = result.config;
+
+ // Find the starting pattern in the "resourcepool-path" entry in the ConfigMap
+ const resourcePoolMatch = cfg.match(/resourcepool-path\s*=\s*["']?([^"'\n\r]+)["']?/);
+ if (resourcePoolMatch) {
+ const resourcePoolPathLine = resourcePoolMatch[0];
+ const resourcePoolPathValue = resourcePoolMatch[1].trim();
+
+ // Check only the starting pattern, to prevent the additional path segments from breaking the exact match comparison
+ const poolPathStartingPattern = `/${initDatacenter}/host/${initVCenterCluster}/Resources`;
+ if (resourcePoolPathValue.startsWith(poolPathStartingPattern)) {
+ // Extract any additional path segments after /Resources to preserve them
+ const additionalSegments = resourcePoolPathValue.substring(poolPathStartingPattern.length);
+ const newResourcePoolPath = `/${datacenter}/host/${vCenterCluster}/Resources${additionalSegments}`;
+ return {
+ config: cfg.replace(resourcePoolPathLine, `resourcepool-path = "${newResourcePoolPath}"`),
+ expectedValues: result.expectedValues,
+ };
+ }
+ }
+
+ // As a fallback, only exact matches are supported (this would not preserve additional path segments)
+ const initResourcePoolPath = `/${initDatacenter}/host/${initVCenterCluster}/Resources`;
+ const newResourcePoolPath = `/${datacenter}/host/${vCenterCluster}/Resources`;
+ return getUpdatedConfig(
+ result,
+ `resourcepool-path = "${initResourcePoolPath}"`,
+ `resourcepool-path = "${newResourcePoolPath}"`,
+ );
};
const updateIniFormat = (
@@ -174,52 +254,63 @@ const updateIniFormat = (
initValues: ConnectionFormFormikValues,
cloudProviderConfig: ConfigMap,
): string => {
- let cfg = cloudProviderConfig.data.config;
+ const cfg = cloudProviderConfig.data.config;
const initVCenter = initValues.vcenter || 'vcenterplaceholder';
const initVCenterCluster = initValues.vCenterCluster || 'clusterplaceholder';
const initDatacenter = initValues.datacenter || 'datacenterplaceholder';
const initDatastore =
initValues.defaultDatastore || '/datacenterplaceholder/datastore/defaultdatastoreplaceholder';
- const initFolder = initValues.folder || '/datacenterplaceholder/vm/folderplaceholder';
- cfg = findAndReplace(
- cfg,
+ let result: UpdateConfigMapResult = { config: cfg, expectedValues: [] };
+
+ result = getUpdatedConfig(
+ result,
`[VirtualCenter "${initVCenter}"]`,
`[VirtualCenter "${values.vcenter}"]`,
);
- cfg = findAndReplace(cfg, `server = "${initVCenter}"`, `server = "${values.vcenter}"`);
- cfg = findAndReplace(
- cfg,
+ result = getUpdatedConfig(result, `server = "${initVCenter}"`, `server = "${values.vcenter}"`);
+ result = getUpdatedConfig(
+ result,
`datacenters = "${initDatacenter}"`,
`datacenters = "${values.datacenter}"`,
);
- cfg = findAndReplace(
- cfg,
+ result = getUpdatedConfig(
+ result,
`datacenter = "${initDatacenter}"`,
`datacenter = "${values.datacenter}"`,
);
- cfg = findAndReplace(
- cfg,
+ result = getUpdatedConfig(
+ result,
`default-datastore = "${initDatastore}"`,
`default-datastore = "${values.defaultDatastore}"`,
);
- cfg = findAndReplace(cfg, `folder = "${initFolder}"`, `folder = "${values.folder}"`);
- cfg = findAndReplace(
- cfg,
- `resourcepool-path = "/${initDatacenter}/host/${initVCenterCluster}/Resources"`,
- `resourcepool-path = "/${values.datacenter}/host/${values.vCenterCluster}/Resources"`,
+
+ // "folder" is handled differently, as it can be absent from the "topology" section of the Infrastructure CRD.
+ result = getUpdatedConfigMapFolder(result, initValues.folder, values.folder);
+
+ // "resourcepool-path" is handled differently, as it can take additional path segments that need to be preserved
+ result = getUpdatedConfigMapResourcePool(
+ result,
+ initDatacenter,
+ initVCenterCluster,
+ values.datacenter,
+ values.vCenterCluster,
);
- if (!cfg) {
+ if (result.expectedValues.length > 0) {
throw new PersistError(
- t('Failed to parse cloud provider config {{cm}}', { cm: cloudProviderConfig.metadata.name }),
- t('Unknown format'),
+ t('Failed to parse cloud provider config {{cm}}', {
+ cm: cloudProviderConfig.metadata.name,
+ }),
+ t('The following content was expected to be defined in the configMap: {{ expectedValues }}', {
+ expectedValues: result.expectedValues.join(', '),
+ }),
);
}
- return cfg;
+ return result.config;
};
// https://issues.redhat.com/browse/OCPBUGS-54434
@@ -374,6 +465,27 @@ const getAddTaintsOps = async (nodesModel: K8sModel): Promise => {
return patchRequests;
};
+// Gets the updated resource pool path for the infrastructure CRD in the format:
+// /{datacenter}/host/{vCenterCluster}/Resources/{additionalSegments}
+// Additional segments present in the value are respected.
+const getInfrastructureResourcePoolPath = (
+ values: ConnectionFormFormikValues,
+ initValues: ConnectionFormFormikValues,
+ originalResourcePool: string,
+): string => {
+ const initDatacenter = initValues.datacenter || 'datacenterplaceholder';
+ const initVCenterCluster = initValues.vCenterCluster || 'clusterplaceholder';
+ const expectedResourcePoolPattern = `/${initDatacenter}/host/${initVCenterCluster}/Resources`;
+
+ let newResourcePool = `/${values.datacenter}/host/${values.vCenterCluster}/Resources`;
+ if (originalResourcePool && originalResourcePool.startsWith(expectedResourcePoolPattern)) {
+ // Preserve additional path segments after /Resources
+ const additionalSegments = originalResourcePool.substring(expectedResourcePoolPattern.length);
+ newResourcePool = `/${values.datacenter}/host/${values.vCenterCluster}/Resources${additionalSegments}`;
+ }
+ return newResourcePool;
+};
+
const getPersistInfrastructureOp = async (
infrastructureModel: K8sModel,
values: ConnectionFormFormikValues,
@@ -394,9 +506,16 @@ const getPersistInfrastructureOp = async (
vCenterDomainCfg.topology.computeCluster = `/${values.datacenter}/host/${values.vCenterCluster}`;
vCenterDomainCfg.topology.datacenter = values.datacenter;
vCenterDomainCfg.topology.datastore = values.defaultDatastore;
- vCenterDomainCfg.topology.networks = [values.vCenterCluster];
+ if (values.network) {
+ vCenterDomainCfg.topology.networks = [values.network];
+ }
vCenterDomainCfg.topology.folder = values.folder;
- vCenterDomainCfg.topology.resourcePool = `/${values.datacenter}/host/${values.vCenterCluster}/Resources`;
+
+ vCenterDomainCfg.topology.resourcePool = getInfrastructureResourcePoolPath(
+ values,
+ initValues,
+ vCenterDomainCfg.topology.resourcePool,
+ );
const vCenterCfg = initValues.vcenter
? infrastructure.spec.platformSpec.vsphere.vcenters.find((c) => c.server === initValues.vcenter)
diff --git a/frontend/packages/vsphere-plugin/src/components/types.ts b/frontend/packages/vsphere-plugin/src/components/types.ts
index 823bd0f0d91..a5ce95c8594 100644
--- a/frontend/packages/vsphere-plugin/src/components/types.ts
+++ b/frontend/packages/vsphere-plugin/src/components/types.ts
@@ -9,6 +9,7 @@ export type ConnectionFormFormikValues = {
defaultDatastore: string;
folder: string;
vCenterCluster: string;
+ network: string; // Primary network name
isInit?: boolean;
};
diff --git a/frontend/packages/vsphere-plugin/src/hooks/use-connection-form.ts b/frontend/packages/vsphere-plugin/src/hooks/use-connection-form.ts
index b3c8dbd4e80..818c6b7444e 100644
--- a/frontend/packages/vsphere-plugin/src/hooks/use-connection-form.ts
+++ b/frontend/packages/vsphere-plugin/src/hooks/use-connection-form.ts
@@ -40,6 +40,7 @@ const initialLoad = async (
username: '',
vcenter: '',
vCenterCluster: '',
+ network: '',
isInit: vCenterServer === 'vcenterplaceholder',
};
}
@@ -47,8 +48,13 @@ const initialLoad = async (
const datacenter = vSphereFailureDomain.topology?.datacenter || '';
const defaultDatastore = vSphereFailureDomain.topology?.datastore || '';
const folder = vSphereFailureDomain.topology?.folder || '';
- const vcenter = vSphereCfg.vcenters?.[0]?.server || '';
- const vCenterCluster = vSphereFailureDomain.topology.networks[0] || '';
+
+ // Extract cluster name from computeCluster path (format: /{datacenter}/host/{cluster})
+ const computeCluster = vSphereFailureDomain.topology?.computeCluster || '';
+ const vCenterCluster = computeCluster.match(/\/.*?\/host\/(.+)/)?.[1] || '';
+
+ // Load the primary network (first network in the networks array)
+ const network = vSphereFailureDomain.topology?.networks?.[0] || '';
let username = '';
let password = '';
@@ -65,8 +71,8 @@ const initialLoad = async (
}
const secretKeyValues = secret.data || {};
- username = decodeBase64(secretKeyValues[`${vcenter}.username`]);
- password = decodeBase64(secretKeyValues[`${vcenter}.password`]);
+ username = decodeBase64(secretKeyValues[`${vCenterServer}.username`]);
+ password = decodeBase64(secretKeyValues[`${vCenterServer}.password`]);
} catch (e) {
// It should be there if referenced
// eslint-disable-next-line no-console
@@ -80,8 +86,9 @@ const initialLoad = async (
datacenter,
defaultDatastore,
folder,
- vcenter,
+ vcenter: vCenterServer,
vCenterCluster,
+ network,
password,
username,
};