Francesco Torchia 4f2688f6ab
Add pkg/harvester components + shell portings - 1
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-10-23 17:00:46 +02:00

307 lines
8.6 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 } from '../../types';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { OS } from './index';
import { parseVolumeClaimTemplates } from '../../utils/vm';
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;
},
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;
},
},
};