Andy Lee f02074a25e Extract variables to utils/unit
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 8db4de1c4a1200f4f303e4444b6435c581187d72)
2025-03-04 02:47:42 +00:00

230 lines
8.3 KiB
JavaScript

import { PVC } from '@shell/config/types';
import { isValidMac, isValidDNSLabelName } from '@pkg/utils/regular';
import { SOURCE_TYPE } from '@pkg/config/harvester-map';
import { parseVolumeClaimTemplates } from '@pkg/utils/vm';
import { GIBIBYTE } from '../utils/unit';
const maxNameLength = 63;
export function vmNetworks(spec, getters, errors, validatorArgs) {
const { domain: { devices: { interfaces } }, networks } = spec;
const networkNames = [];
interfaces.map( (I, index) => {
const N = networks.find( (N) => I.name === N.name);
const prefix = (I.name || N.name) || `Network ${ index + 1 }`;
const type = getters['i18n/t']('harvester.fields.network');
const lowerType = getters['i18n/t']('harvester.validation.vm.network.lowerType');
const upperType = getters['i18n/t']('harvester.validation.vm.network.upperType');
validName(getters, errors, I.name, networkNames, prefix, type, lowerType, upperType);
if (N.multus) {
if (!N.multus.networkName) {
const key = getters['i18n/t']('harvester.fields.network');
const message = getters['i18n/t']('validation.required', { key });
errors.push(getters['i18n/t']('harvester.validation.generic.tabError', { prefix, message }));
}
}
if (I.macAddress && !isValidMac(I.macAddress) && !N.pod) {
const message = getters['i18n/t']('harvester.validation.vm.network.macFormat');
errors.push(getters['i18n/t']('harvester.validation.generic.tabError', { prefix, message }));
}
});
return errors;
}
export function vmDisks(spec, getters, errors, validatorArgs, displayKey, value) {
const isVMTemplate = validatorArgs.includes('isVMTemplate');
const data = isVMTemplate ? this.value.spec.vm : value;
const _volumeClaimTemplates = parseVolumeClaimTemplates(data);
const _volumes = spec.template.spec.volumes || [];
const _disks = spec.template.spec.domain.devices.disks || [];
const diskNames = [];
_disks.forEach((D, idx) => {
const prefix = D.name || _volumes[idx]?.name || `Volume ${ idx + 1 }`;
if (!D.disk && !D.cdrom) {
const key = getters['i18n/t']('harvester.fields.type');
const message = getters['i18n/t']('validation.required', { key });
errors.push(getters['i18n/t']('harvester.validation.generic.tabError', { prefix, message }));
}
const type = getters['i18n/t']('harvester.fields.volume');
const lowerType = getters['i18n/t']('harvester.validation.vm.volume.lowerType');
const upperType = getters['i18n/t']('harvester.validation.vm.volume.upperType');
validName(getters, errors, D.name, diskNames, prefix, type, lowerType, upperType);
});
let requiredVolume = false;
_volumes.forEach((V, idx) => {
const { type, typeValue } = getVolumeType(getters, V, _volumeClaimTemplates, value);
const prefix = V.name || idx + 1;
if ([SOURCE_TYPE.IMAGE, SOURCE_TYPE.ATTACH_VOLUME, SOURCE_TYPE.CONTAINER].includes(type)) {
requiredVolume = true;
}
if (type === SOURCE_TYPE.NEW || type === SOURCE_TYPE.IMAGE) {
if (!/([1-9]|[1-9][0-9]+)[a-zA-Z]+/.test(typeValue?.spec?.resources?.requests?.storage)) {
const key = getters['i18n/t']('harvester.fields.size');
const message = getters['i18n/t']('validation.required', { key });
errors.push(getters['i18n/t']('harvester.validation.generic.tabError', { prefix, message }));
}
if (typeValue?.spec?.resources?.requests?.storage && !/^([0-9][0-9]{0,8})[a-zA-Z]+$/.test(typeValue?.spec?.resources?.requests?.storage)) {
const message = getters['i18n/t']('harvester.validation.generic.maximumSize', { max: `999999999 ${ GIBIBYTE }` });
errors.push(getters['i18n/t']('harvester.validation.generic.tabError', { prefix, message }));
}
if (type === SOURCE_TYPE.IMAGE && !typeValue?.spec?.storageClassName && !isVMTemplate) { // type === SOURCE_TYPE.IMAGE
const key = getters['i18n/t']('harvester.fields.image');
const message = getters['i18n/t']('validation.required', { key });
errors.push(getters['i18n/t']('harvester.validation.generic.tabError', { prefix, message }));
}
if (!typeValue?.spec?.storageClassName && V?.persistentVolumeClaim?.claimName && type !== SOURCE_TYPE.IMAGE) {
const key = getters['i18n/t']('harvester.fields.storageClass');
const message = getters['i18n/t']('validation.required', { key });
errors.push(getters['i18n/t']('harvester.validation.generic.tabError', { prefix, message }));
}
}
if (type === SOURCE_TYPE.ATTACH_VOLUME) {
const allPVCs = getters['harvester/all'](PVC);
const selectedVolumeName = V?.persistentVolumeClaim?.claimName;
const hasExistingVolume = allPVCs.find((P) => P.id === `${ value.metadata.namespace }/${ selectedVolumeName }`);
if (!hasExistingVolume && selectedVolumeName) { // selected volume may have been deleted. e.g: use template
const type = getters['i18n/t']('harvester.fields.volume');
errors.push(getters['i18n/t']('harvester.validation.generic.hasDelete', { type, name: selectedVolumeName }));
}
if (!selectedVolumeName) { // volume is not selected.
const key = getters['i18n/t']('harvester.virtualMachine.volume.volume');
errors.push(getters['i18n/t']('validation.required', { key }));
}
}
if (type === SOURCE_TYPE.CONTAINER && !V.containerDisk.image) {
const key = getters['i18n/t']('harvester.fields.dockerImage');
const message = getters['i18n/t']('validation.required', { key });
errors.push(getters['i18n/t']('harvester.validation.generic.tabError', { prefix, message }));
}
});
/**
* At least one volume must be create. (Verify only when create.)
*/
if ((!requiredVolume || _volumes.length === 0) && !value.links) {
errors.push(getters['i18n/t']('harvester.validation.vm.volume.needImageOrExisting'));
}
return errors;
}
function getVolumeType(getters, V, DVTS, value) {
let outValue = null;
const allPVCs = getters['harvester/all'](PVC);
if (V.persistentVolumeClaim) {
const selectedVolumeName = V?.persistentVolumeClaim?.claimName;
const hasExistingVolume = allPVCs.find((P) => P.id === `${ value.metadata.namespace }/${ selectedVolumeName }`);
if (hasExistingVolume) {
// In other cases, claimName will not be empty, so we can judge whether this is an exiting volume based on this attribute
return {
type: SOURCE_TYPE.ATTACH_VOLUME,
typeValue: null
};
}
outValue = DVTS.find((DVT) => {
return V.persistentVolumeClaim.claimName === DVT.metadata.name && DVT.metadata?.annotations && Object.prototype.hasOwnProperty.call(DVT.metadata.annotations, 'harvesterhci.io/imageId');
});
if (outValue) {
return {
type: SOURCE_TYPE.IMAGE,
typeValue: outValue
};
}
// new type
outValue = DVTS.find((DVT) => V.persistentVolumeClaim.claimName === DVT.metadata.name);
if (outValue) {
return {
type: SOURCE_TYPE.NEW,
typeValue: outValue
};
}
}
if (V.containerDisk) {
return {
type: SOURCE_TYPE.CONTAINER,
typeValue: null
};
}
return {};
}
function validName(getters, errors, name, names = [], prefix, type, lowerType, upperType) {
// Verify that the name is duplicate
if (names.findIndex( (N) => name === N) !== -1) {
errors.push(getters['i18n/t']('harvester.validation.vm.duplicatedName', { type, name }));
}
names.push(name);
// The maximum length of volume name is 63 characters.
if (name && name?.length > maxNameLength) {
const key = getters['i18n/t']('harvester.fields.name');
const message = getters['i18n/t']('harvester.validation.generic.maxLength', { key, max: maxNameLength });
errors.push(getters['i18n/t']('harvester.validation.generic.tabError', { prefix, message }));
}
// name required
if (!name) {
const key = getters['i18n/t']('harvester.fields.name');
const message = getters['i18n/t']('validation.required', { key });
errors.push(getters['i18n/t']('harvester.validation.generic.tabError', { prefix, message }));
}
// valid RFC 1123
if (!isValidDNSLabelName(name)) {
const regex = '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$';
errors.push(getters['i18n/t']('harvester.validation.generic.regex', {
lowerType, name, regex, upperType
}));
}
}