Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export const VSphereConnectionModal: FC<VSphereConnectionProps> = ({

const models = useConnectionModels();

const { initValues, isLoaded, error: loadError } = useConnectionForm();
const { initValues, isLoaded, error: loadError } = useConnectionForm(cloudProviderConfig);

const onClose = () => {
setModalOpen(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { parseKeyValue, encodeBase64, decodeBase64 } from '../utils';

describe('parseKeyValue', () => {
it('should parse INI-style key=value pairs', () => {
const config = `server = "vcenter.example.com"
datacenter = "dc1"
default-datastore = "/dc1/datastore/vsanDatastore"`;

const result = parseKeyValue(config);

expect(result.server).toBe('vcenter.example.com');
expect(result.datacenter).toBe('dc1');
expect(result['default-datastore']).toBe('/dc1/datastore/vsanDatastore');
});

it('should handle values without quotes', () => {
const config = `server = vcenter.example.com
datacenter = dc1`;

const result = parseKeyValue(config);

expect(result.server).toBe('vcenter.example.com');
expect(result.datacenter).toBe('dc1');
});

it('should skip section headers and empty lines', () => {
const config = `[Global]
secret-name = "vsphere-creds"

[Workspace]
server = "vcenter.example.com"`;

const result = parseKeyValue(config);

expect(result['secret-name']).toBe('vsphere-creds');
expect(result.server).toBe('vcenter.example.com');
expect(Object.keys(result)).not.toContain('[Global]');
});

it('should parse a full INI-format vSphere ConfigMap', () => {
const config = `[Global]
secret-name = "vsphere-creds"
secret-namespace = "kube-system"

[Workspace]
server = "vcenter.example.com"
datacenter = "dc1"
default-datastore = "/dc1/datastore/vsanDatastore"
folder = "/dc1/vm/myfolder"
resourcepool-path = "/dc1/host/mycluster/Resources/mypool"

[VirtualCenter "vcenter.example.com"]
datacenters = "dc1"`;

const result = parseKeyValue(config);

expect(result['secret-name']).toBe('vsphere-creds');
expect(result['secret-namespace']).toBe('kube-system');
expect(result.server).toBe('vcenter.example.com');
expect(result.datacenter).toBe('dc1');
expect(result['default-datastore']).toBe('/dc1/datastore/vsanDatastore');
expect(result.folder).toBe('/dc1/vm/myfolder');
expect(result['resourcepool-path']).toBe('/dc1/host/mycluster/Resources/mypool');
});

it('should return empty object for empty string', () => {
expect(parseKeyValue('')).toEqual({});
});
});

describe('encodeBase64 / decodeBase64', () => {
it('should round-trip a string', () => {
const original = 'admin@vsphere.local';
expect(decodeBase64(encodeBase64(original))).toBe(original);
});
});
16 changes: 16 additions & 0 deletions frontend/packages/vsphere-plugin/src/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ import type { ConsoleTFunction } from '@console/dynamic-plugin-sdk';
export const encodeBase64 = (data: string) => Buffer.from(data).toString('base64');
export const decodeBase64 = (data: string) => Buffer.from(data, 'base64').toString('ascii');

export const parseKeyValue = (config: string, delimiter = '='): { [key: string]: string } => {
const lines = config.split('\n');
const result: { [key: string]: string } = {};
lines.forEach((line) => {
const idx = line.indexOf(delimiter);
if (idx > 0) {
const key = line.substring(0, idx).trim();
let value = line.substring(idx + 1).trim();
if (value.charAt(0) === '"') value = value.substring(1);
if (value.charAt(value.length - 1) === '"') value = value.substring(0, value.length - 1);
result[key] = value;
}
});
return result;
};

export const getErrorMessage = (t: ConsoleTFunction, error: unknown): string => {
if (error instanceof Error) {
return error.message || '';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { k8sGet } from '@console/dynamic-plugin-sdk/src/api/core-api';
import { encodeBase64 } from '../../components/utils';
import type { ConfigMap } from '../../resources/configMap';
import type { Infrastructure } from '../../resources/infrastructure';
import { initialLoad } from '../use-connection-form';

jest.mock('@console/dynamic-plugin-sdk/src/api/core-api', () => ({
k8sGet: jest.fn(),
}));

const k8sGetMock = k8sGet as jest.Mock;

const secretModel = { kind: 'Secret', apiVersion: 'v1' } as any;
const infrastructureModel = {
kind: 'Infrastructure',
apiVersion: 'config.openshift.io/v1',
} as any;

const makeInfrastructure = (
overrides: Partial<Infrastructure['spec']['platformSpec']['vsphere']> = {},
): Infrastructure =>
({
spec: {
platformSpec: {
type: 'VSphere',
vsphere: {
vcenters: [{ server: 'vcenter.example.com', datacenters: ['dc1'] }],
failureDomains: [
{
name: 'fd1',
topology: {
datacenter: 'dc1',
datastore: '/dc1/datastore/vsanDatastore',
folder: '/dc1/vm/myfolder',
computeCluster: '/dc1/host/mycluster',
networks: ['network1'],
},
},
],
...overrides,
},
},
},
status: { platform: 'VSphere' },
}) as Infrastructure;

const makeSecret = (vcenter: string, username: string, password: string) => ({
data: {
[`${vcenter}.username`]: encodeBase64(username),
[`${vcenter}.password`]: encodeBase64(password),
},
});

const makeIniConfigMap = (): ConfigMap =>
({
metadata: { name: 'cloud-provider-config', namespace: 'openshift-config' },
data: {
config: `[Global]
secret-name = "vsphere-creds"
secret-namespace = "kube-system"

[Workspace]
server = "vcenter.example.com"
datacenter = "dc1"
default-datastore = "/dc1/datastore/vsanDatastore"
folder = "/dc1/vm/myfolder"
resourcepool-path = "/dc1/host/mycluster/Resources/mypool"

[VirtualCenter "vcenter.example.com"]
datacenters = "dc1"`,
},
}) as ConfigMap;

describe('initialLoad', () => {
afterEach(() => {
jest.resetAllMocks();
});

it('should read from failureDomains when populated', async () => {
k8sGetMock
.mockResolvedValueOnce(makeInfrastructure())
.mockResolvedValueOnce(makeSecret('vcenter.example.com', 'admin', 's3cret'));

const result = await initialLoad(secretModel, infrastructureModel);

expect(result.vcenter).toBe('vcenter.example.com');
expect(result.datacenter).toBe('dc1');
expect(result.defaultDatastore).toBe('/dc1/datastore/vsanDatastore');
expect(result.folder).toBe('/dc1/vm/myfolder');
expect(result.vCenterCluster).toBe('mycluster');
expect(result.network).toBe('network1');
expect(result.username).toBe('admin');
expect(result.password).toBe('s3cret');
});

it('should fall back to ConfigMap when failureDomains is empty', async () => {
k8sGetMock
.mockResolvedValueOnce(makeInfrastructure({ failureDomains: [] }))
.mockResolvedValueOnce(makeSecret('vcenter.example.com', 'admin', 's3cret'));

const result = await initialLoad(secretModel, infrastructureModel, makeIniConfigMap());

expect(result.vcenter).toBe('vcenter.example.com');
expect(result.datacenter).toBe('dc1');
expect(result.defaultDatastore).toBe('/dc1/datastore/vsanDatastore');
expect(result.folder).toBe('/dc1/vm/myfolder');
expect(result.vCenterCluster).toBe('mycluster');
expect(result.network).toBe('');
expect(result.username).toBe('admin');
expect(result.password).toBe('s3cret');
});

it('should fall back to ConfigMap when failureDomains is undefined', async () => {
k8sGetMock
.mockResolvedValueOnce(makeInfrastructure({ failureDomains: undefined }))
.mockResolvedValueOnce(makeSecret('vcenter.example.com', 'admin', 's3cret'));

const result = await initialLoad(secretModel, infrastructureModel, makeIniConfigMap());

expect(result.vcenter).toBe('vcenter.example.com');
expect(result.datacenter).toBe('dc1');
expect(result.defaultDatastore).toBe('/dc1/datastore/vsanDatastore');
expect(result.folder).toBe('/dc1/vm/myfolder');
});

it('should return empty values when failureDomains is empty and no ConfigMap', async () => {
k8sGetMock.mockResolvedValueOnce(makeInfrastructure({ failureDomains: [] }));

const result = await initialLoad(secretModel, infrastructureModel);

expect(result.vcenter).toBe('');
expect(result.datacenter).toBe('');
expect(result.defaultDatastore).toBe('');
expect(result.folder).toBe('');
expect(result.vCenterCluster).toBe('');
expect(result.network).toBe('');
expect(result.username).toBe('');
expect(result.password).toBe('');
});

it('should return empty values with isInit when vCenter is placeholder', async () => {
k8sGetMock.mockResolvedValueOnce(
makeInfrastructure({
failureDomains: [],
vcenters: [{ server: 'vcenterplaceholder', datacenters: ['dc1'] }],
} as any),
);

const result = await initialLoad(secretModel, infrastructureModel, makeIniConfigMap());

expect(result.vcenter).toBe('');
expect(result.isInit).toBe(true);
});

it('should extract vCenterCluster from resourcepool-path in ConfigMap fallback', async () => {
k8sGetMock
.mockResolvedValueOnce(makeInfrastructure({ failureDomains: [] }))
.mockResolvedValueOnce(makeSecret('vcenter.example.com', 'admin', 's3cret'));

const configMap: ConfigMap = {
metadata: { name: 'cloud-provider-config', namespace: 'openshift-config' },
data: {
config: `server = "vcenter.example.com"
datacenter = "dc1"
default-datastore = "/dc1/datastore/ds1"
resourcepool-path = "/dc1/host/deep-cluster/Resources/pool"`,
},
} as ConfigMap;

const result = await initialLoad(secretModel, infrastructureModel, configMap);

expect(result.vCenterCluster).toBe('deep-cluster');
});
});
Loading