mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2025-12-13 21:21:44 +00:00
311 lines
8.7 KiB
JavaScript
311 lines
8.7 KiB
JavaScript
import YAML from 'yaml';
|
|
import jsyaml from 'js-yaml';
|
|
import isEqual from 'lodash/isEqual';
|
|
import { clone } from '@shell/utils/object';
|
|
import { SECRET } from '@shell/config/types';
|
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
|
import { HCI } from '../../types';
|
|
import { parseVolumeClaimTemplates } from '../../utils/vm';
|
|
import { OS } from './index';
|
|
|
|
export const QGA_JSON = {
|
|
package_update: true,
|
|
packages: ['qemu-guest-agent'],
|
|
runcmd: [
|
|
[
|
|
'systemctl',
|
|
'enable',
|
|
'--now',
|
|
'qemu-guest-agent.service'
|
|
]
|
|
]
|
|
};
|
|
|
|
export const QGA_MAP = { default: 'qemu-guest-agent.service' };
|
|
|
|
export const USB_TABLET = [{
|
|
bus: 'usb',
|
|
name: 'tablet',
|
|
type: 'tablet'
|
|
}];
|
|
|
|
export const SSH_EXISTING_TYPE = {
|
|
EXISTING_ALL: 'EXISTING_ALL',
|
|
EXISTING_ONLY_ANNOTATION: 'EXISTING_ANNOTATION',
|
|
EXISTING_ONLY_CLOUD: 'EXISTING_CLOUD',
|
|
};
|
|
|
|
export default {
|
|
methods: {
|
|
hasCloudConfigComment(userScript) {
|
|
// Check that userData contains: #cloud-config
|
|
const userDataDoc = userScript ? YAML.parseDocument(userScript) : YAML.parseDocument({});
|
|
const items = userDataDoc?.contents?.items || [];
|
|
|
|
let exist = false;
|
|
|
|
if (userDataDoc?.comment === 'cloud-config' || userDataDoc?.comment?.includes('cloud-config\n')) {
|
|
exist = true;
|
|
}
|
|
|
|
if (userDataDoc?.commentBefore === 'cloud-config' || userDataDoc?.commentBefore?.includes('cloud-config\n')) {
|
|
exist = true;
|
|
}
|
|
|
|
items.map((item) => {
|
|
const key = item.key;
|
|
|
|
if (key?.commentBefore === 'cloud-config' || key?.commentBefore?.includes('cloud-config\n')) {
|
|
exist = true;
|
|
}
|
|
});
|
|
|
|
return exist;
|
|
},
|
|
|
|
getSSHValue(id) {
|
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
const sshs = this.$store.getters[`${ inStore }/all`](HCI.SSH) || [];
|
|
|
|
return sshs.find( O => O.id === id)?.spec?.publicKey || undefined;
|
|
},
|
|
|
|
getOsType(vm) {
|
|
return vm.metadata?.labels?.[HCI_ANNOTATIONS.OS];
|
|
},
|
|
|
|
getMatchQGA(osType) {
|
|
const _QGA_JSON = clone(QGA_JSON);
|
|
let hasCustomQGA = false;
|
|
|
|
OS.forEach((O) => {
|
|
if (O.match) {
|
|
hasCustomQGA = O.match.find(type => type === osType);
|
|
}
|
|
});
|
|
|
|
if (hasCustomQGA) {
|
|
_QGA_JSON.runcmd[0][3] = QGA_MAP[osType];
|
|
} else {
|
|
_QGA_JSON.runcmd[0][3] = QGA_MAP['default'];
|
|
}
|
|
|
|
return _QGA_JSON;
|
|
},
|
|
|
|
getSimilarRuncmd(osType) {
|
|
const _QGA_JSON = clone(QGA_JSON);
|
|
|
|
if (osType === 'openSUSE') {
|
|
_QGA_JSON.runcmd[0][3] = QGA_MAP['default'];
|
|
} else {
|
|
_QGA_JSON.runcmd[0][3] = QGA_MAP['suse'];
|
|
}
|
|
|
|
return _QGA_JSON.runcmd[0];
|
|
},
|
|
|
|
hasInstallAgent(userScript, osType, oldValue) {
|
|
let dataFormat = {};
|
|
const _QGA_JSON = this.getMatchQGA(osType);
|
|
|
|
try {
|
|
dataFormat = jsyaml.load(userScript) || {};
|
|
} catch (e) {
|
|
new Error('Function(hasInstallAgent) error');
|
|
|
|
return oldValue;
|
|
}
|
|
|
|
return dataFormat?.packages?.includes('qemu-guest-agent') && !!dataFormat?.runcmd?.find( S => Array.isArray(S) && S.join('-') === _QGA_JSON.runcmd[0].join('-'));
|
|
},
|
|
|
|
isInstallUSBTablet(spec) {
|
|
const inputs = spec?.template?.spec?.domain?.devices?.inputs;
|
|
|
|
if (Array.isArray(inputs)) {
|
|
return !!inputs.find((O) => {
|
|
return isEqual(O, USB_TABLET[0]);
|
|
});
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
isEfiEnabled(spec) {
|
|
return !!(spec?.template?.spec?.domain?.firmware?.bootloader?.efi);
|
|
},
|
|
|
|
isTpmEnabled(spec) {
|
|
return !!spec?.template?.spec?.domain?.devices?.tpm ;
|
|
},
|
|
|
|
isSecureBoot(spec) {
|
|
return !!spec?.template?.spec?.domain?.firmware?.bootloader?.efi?.secureBoot;
|
|
},
|
|
|
|
isCpuPinning(spec) {
|
|
return !!spec?.template?.spec?.domain?.cpu?.dedicatedCpuPlacement;
|
|
},
|
|
|
|
getCloudInitNoCloud(spec) {
|
|
const secret = this.getSecret(spec);
|
|
let userData = secret?.decodedData?.userdata;
|
|
let networkData = secret?.decodedData?.networkdata;
|
|
|
|
const cloudInitNoCloud = spec?.template?.spec?.volumes?.find( (V) => {
|
|
return V.name === 'cloudinitdisk';
|
|
})?.cloudInitNoCloud || {};
|
|
|
|
// If the value is not found inside the secret, the data may be written directly in the yaml
|
|
if (cloudInitNoCloud?.userData) {
|
|
userData = cloudInitNoCloud.userData;
|
|
this.saveUserDataAsClearText = true;
|
|
}
|
|
|
|
if (cloudInitNoCloud?.networkData) {
|
|
networkData = cloudInitNoCloud.networkData;
|
|
this.saveNetworkDataAsClearText = true;
|
|
}
|
|
|
|
return { userData, networkData };
|
|
},
|
|
|
|
getSecret(spec) {
|
|
const cloudInitNoCloud = spec?.template?.spec?.volumes?.find( (V) => {
|
|
return V.name === 'cloudinitdisk';
|
|
})?.cloudInitNoCloud || {};
|
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
const secrets = this.$store.getters[`${ inStore }/all`](SECRET) || [];
|
|
|
|
const secretName = cloudInitNoCloud?.secretRef?.name || cloudInitNoCloud?.networkDataSecretRef?.name;
|
|
|
|
const secret = secrets.find(s => s.metadata.name === secretName);
|
|
|
|
return secret;
|
|
},
|
|
|
|
getAccessCredentials(spec) {
|
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
const secrets = this.$store.getters[`${ inStore }/all`](SECRET) || [];
|
|
const credentials = spec?.template?.spec?.accessCredentials || [];
|
|
const annotations = JSON.parse(spec.template.metadata?.annotations?.[HCI_ANNOTATIONS.DYNAMIC_SSHKEYS_NAMES] || '[]');
|
|
|
|
return credentials.map((c) => {
|
|
const source = !!c.userPassword ? 'userPassword' : 'sshPublicKey';
|
|
const secretName = c[source]?.source?.secret?.secretName;
|
|
const secretRef = secrets.find(s => s.metadata.name === secretName);
|
|
const out = {
|
|
source, username: '', newPassword: '', users: [], sshkeys: [], secretName, secretRef
|
|
};
|
|
|
|
if (!secretRef) {
|
|
out.secretRef = undefined;
|
|
} else if (source === 'userPassword') {
|
|
const username = Object.keys(secretRef?.data)[0];
|
|
const newPassword = secretRef.decodedData[username];
|
|
|
|
out.username = username;
|
|
out.newPassword = newPassword;
|
|
} else {
|
|
const users = c[source].propagationMethod.qemuGuestAgent.users;
|
|
const sshkeys = annotations?.[secretName];
|
|
|
|
out.users = users;
|
|
out.sshkeys = sshkeys;
|
|
}
|
|
|
|
return out;
|
|
});
|
|
},
|
|
|
|
getRootImageId(vm) {
|
|
const volumes = parseVolumeClaimTemplates(vm);
|
|
|
|
return volumes?.[0]?.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID] || '';
|
|
},
|
|
|
|
getSSHFromAnnotation(spec) {
|
|
const ids = spec?.template?.metadata?.annotations?.[HCI_ANNOTATIONS.SSH_NAMES] || '[]';
|
|
|
|
return JSON.parse(ids);
|
|
},
|
|
|
|
convertToJson(script = '') {
|
|
let out = {};
|
|
|
|
try {
|
|
out = jsyaml.load(script);
|
|
} catch (e) {
|
|
new Error('Function(convertToJson) error');
|
|
}
|
|
|
|
return out;
|
|
},
|
|
|
|
getSSHFromUserData(userData) {
|
|
return this.convertToJson(userData)?.ssh_authorized_keys || [];
|
|
},
|
|
|
|
compareSSHValue(a = '', b = '') {
|
|
const r = /(\r\n\t|\n|\r\t)|(\s*)/gm;
|
|
|
|
return a.replace(r, '') === b.replace(r, '');
|
|
},
|
|
|
|
mergeAllSSHs(spec) {
|
|
const keys = this.getSSHFromAnnotation(spec);
|
|
const { userScript: userData } = this.getCloudInitNoCloud(spec);
|
|
|
|
if (!keys?.length < 0 && !userData) {
|
|
return [];
|
|
}
|
|
|
|
let out = [];
|
|
|
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
const allSSHs = this.$store.getters[`${ inStore }/all`](HCI.SSH) || [];
|
|
|
|
out = (keys || []).map((id) => {
|
|
const hasSSHResource = allSSHs.find(ssh => ssh.id === id);
|
|
|
|
if (hasSSHResource) {
|
|
return {
|
|
id: hasSSHResource.id,
|
|
data: hasSSHResource,
|
|
type: SSH_EXISTING_TYPE.EXISTING_ALL
|
|
};
|
|
} else {
|
|
return {
|
|
id,
|
|
data: id,
|
|
type: SSH_EXISTING_TYPE.EXISTING_ONLY_ANNOTATION
|
|
};
|
|
}
|
|
});
|
|
|
|
const _userDataSSH = this.getSSHFromUserData(userData);
|
|
|
|
_userDataSSH.map( (sshValue) => {
|
|
const hasSSHResource = allSSHs.find(ssh => this.compareSSHValue(sshValue, ssh.spec?.publicKey));
|
|
|
|
if (hasSSHResource && !out.find(O => O.id === hasSSHResource.id)) {
|
|
out.push({
|
|
id: hasSSHResource.id,
|
|
data: hasSSHResource,
|
|
type: SSH_EXISTING_TYPE.EXISTING_ALL
|
|
});
|
|
} else if (!hasSSHResource) {
|
|
out.push({
|
|
id: 'Unknown',
|
|
data: sshValue,
|
|
type: SSH_EXISTING_TYPE.EXISTING_ONLY_CLOUD
|
|
});
|
|
}
|
|
});
|
|
|
|
return out;
|
|
},
|
|
},
|
|
};
|