mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-01 03:36:04 +08:00
feat: Unattend.xml support for Windows (#954)
Documentation: https://kubevirt.io/user-guide/user_workloads/startup_scripts/#sysprep Related to: https://github.com/harvester/harvester/issues/1836 Signed-off-by: Volker Theile <vtheile@suse.com>
This commit is contained in:
@@ -255,6 +255,29 @@ export default {
|
||||
return this.convertToJson(userData)?.ssh_authorized_keys || [];
|
||||
},
|
||||
|
||||
getSysprepConfig(spec) {
|
||||
const sysprepVolume = spec?.template?.spec?.volumes?.find(
|
||||
(v) => v.name === 'sysprep' && v.sysprep?.secret
|
||||
);
|
||||
|
||||
if (!sysprepVolume) {
|
||||
return { secretName: '', xmlContent: '' };
|
||||
}
|
||||
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const namespace = this.value.metadata.namespace;
|
||||
const secretName = sysprepVolume.sysprep.secret.name;
|
||||
const secret = this.$store.getters[`${ inStore }/byId`](
|
||||
SECRET,
|
||||
`${ namespace }/${ secretName }`
|
||||
);
|
||||
|
||||
return {
|
||||
secretName: `${ namespace }/${ secretName }`,
|
||||
xmlContent: secret?.decodedData?.['autounattend.xml'] || ''
|
||||
};
|
||||
},
|
||||
|
||||
compareSSHValue(a = '', b = '') {
|
||||
const r = /(\r\n\t|\n|\r\t)|(\s*)/gm;
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@ export default {
|
||||
terminationGracePeriodSeconds: '',
|
||||
cpuPinning: false,
|
||||
cpuModel: '',
|
||||
sysprep: { secretName: '', xmlContent: '' },
|
||||
};
|
||||
},
|
||||
|
||||
@@ -445,6 +446,17 @@ export default {
|
||||
this['diskRows'] = diskRows;
|
||||
this['filesystemRows'] = this.getFilesystemRows(vm);
|
||||
|
||||
let sysprepConfig = { secretName: '', xmlContent: '' };
|
||||
|
||||
if (osType === 'windows') {
|
||||
sysprepConfig = this.getSysprepConfig(spec);
|
||||
}
|
||||
|
||||
this['sysprep'] = {
|
||||
secretName: sysprepConfig.secretName || this.sysprep?.secretName || '',
|
||||
xmlContent: sysprepConfig.xmlContent || this.sysprep?.xmlContent || '',
|
||||
};
|
||||
|
||||
this.refreshYamlEditor();
|
||||
},
|
||||
|
||||
@@ -609,7 +621,8 @@ export default {
|
||||
|
||||
out = sortBy(out, 'bootOrder');
|
||||
|
||||
return out.filter( (O) => O.name !== 'cloudinitdisk');
|
||||
// Filter out cloudinitdisk and sysprep disk from UI display.
|
||||
return out.filter( (O) => O.name !== 'cloudinitdisk' && O.name !== 'sysprep');
|
||||
},
|
||||
|
||||
getNetworkRows(vm, config) {
|
||||
@@ -828,6 +841,32 @@ export default {
|
||||
this.secretName = this.generateSecretName(this.secretNamePrefix);
|
||||
}
|
||||
|
||||
if (!disks.find((D) => D.name === 'sysprep') && this.isWindows) {
|
||||
const hasSysprepContent = !!this.sysprep.xmlContent?.trim?.();
|
||||
|
||||
// If we have content but no secret name, it's a new secret that needs a name.
|
||||
if (hasSysprepContent && !this.sysprep.secretName?.trim?.()) {
|
||||
const prefix = this.secretNamePrefix ? `${ this.secretNamePrefix }-windows-sysprep` : 'windows-sysprep';
|
||||
|
||||
this.sysprep.secretName = `${ this.value.metadata.namespace }/${ this.generateSecretName(prefix) }`;
|
||||
}
|
||||
|
||||
// Preserve/attach sysprep whenever a secret is selected/known.
|
||||
if (this.sysprep.secretName) {
|
||||
disks.push({
|
||||
name: 'sysprep',
|
||||
cdrom: { bus: 'sata' }
|
||||
});
|
||||
|
||||
const secretName = this.sysprep.secretName.split('/')[1] || this.sysprep.secretName;
|
||||
|
||||
volumes.push({
|
||||
name: 'sysprep',
|
||||
sysprep: { secret: { name: secretName } }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!disks.find( (D) => D.name === 'cloudinitdisk') && (this.userData || this.networkData)) {
|
||||
if (!this.isWindows) {
|
||||
disks.push({
|
||||
@@ -836,7 +875,6 @@ export default {
|
||||
});
|
||||
|
||||
const userData = this.getUserData({ osType: this.osType, installAgent: this.installAgent });
|
||||
|
||||
const cloudinitdisk = {
|
||||
name: 'cloudinitdisk',
|
||||
cloudInitNoCloud: {}
|
||||
@@ -1482,6 +1520,42 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
async saveSysprepConfig(vm) {
|
||||
if (!this.isWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.sysprep.secretName?.trim?.() && !this.sysprep.xmlContent?.trim?.()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const secretName = this.sysprep.secretName.split('/')[1] || this.sysprep.secretName;
|
||||
const namespace = vm.metadata.namespace;
|
||||
const namespacedName = `${ namespace }/${ secretName }`;
|
||||
|
||||
let secret;
|
||||
|
||||
try {
|
||||
secret = await this.$store.dispatch('harvester/find', { type: SECRET, id: namespacedName });
|
||||
} catch (e) {
|
||||
if (e?.status !== 404) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
secret = await this.$store.dispatch('harvester/create', {
|
||||
type: SECRET,
|
||||
metadata: {
|
||||
name: secretName,
|
||||
namespace,
|
||||
labels: { [HCI_ANNOTATIONS.WINDOWS_SYSPREP]: 'true' },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
secret.setData('autounattend.xml', this.sysprep.xmlContent);
|
||||
await secret.save();
|
||||
},
|
||||
|
||||
getAccessCredentialsValidation() {
|
||||
const errors = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user