From a6bd7ec00e7c8d85f4f097c0dbc61eb9bf5fac56 Mon Sep 17 00:00:00 2001 From: Andy Lee Date: Tue, 28 Jul 2026 14:10:44 +0800 Subject: [PATCH] feat: add VMware VDDK image input field to VM migration provider (#1057) * feat: add VMware VDDK image option to VM migration provider Add an optional VMware VDDK Image field to the Configure Provider step of the VM migration wizard, bound to spec.settings.vddkInitImage when set and left unset when empty. Supports create and edit flows and populates from the existing provider when editing. Signed-off-by: Andy Lee * refactor: copilot review Signed-off-by: Andy Lee --------- Signed-off-by: Andy Lee --- .../vm-migration/ConfigureProviderStep.vue | 72 ++++++++++++++----- pkg/harvester/l10n/en-us.yaml | 3 + .../_cluster/vm-migration/provider-wizard.vue | 2 + .../vm-migration/vm-migration-wizard.vue | 1 + 4 files changed, 59 insertions(+), 19 deletions(-) diff --git a/pkg/harvester/components/vm-migration/ConfigureProviderStep.vue b/pkg/harvester/components/vm-migration/ConfigureProviderStep.vue index 06274372..f4f6783f 100644 --- a/pkg/harvester/components/vm-migration/ConfigureProviderStep.vue +++ b/pkg/harvester/components/vm-migration/ConfigureProviderStep.vue @@ -42,6 +42,7 @@ const { username, password, skipTlsVerify, + vddkInitImage, testPassed, testResult, testError, @@ -97,6 +98,7 @@ watch(selectedProvider, (val) => { username.value = ''; password.value = ''; skipTlsVerify.value = false; + vddkInitImage.value = ''; createdProvider.value = null; createdSecret.value = null; } else { @@ -107,6 +109,7 @@ watch(selectedProvider, (val) => { if (provider) { providerName.value = provider.metadata.name; url.value = provider.spec?.url || ''; + vddkInitImage.value = provider.spec?.settings?.vddkInitImage || ''; const secretRef = provider.spec?.secret; @@ -239,6 +242,18 @@ const testConnection = async(buttonCb) => { if (props.editMode && createdProvider.value) { try { createdProvider.value.spec.url = url.value; + + const vddkImage = vddkInitImage.value?.trim(); + + if (vddkImage) { + createdProvider.value.spec.settings = { + ...(createdProvider.value.spec.settings || {}), + vddkInitImage: vddkImage, + }; + } else if (createdProvider.value.spec.settings) { + delete createdProvider.value.spec.settings.vddkInitImage; + } + await createdProvider.value.save(); const secretRef = createdProvider.value.spec?.secret; @@ -283,20 +298,28 @@ const testConnection = async(buttonCb) => { const namespace = FORKLIFT_NAMESPACE; const secretName = `${ providerName.value }-creds-${ randomStr(4).toLowerCase() }`; + const spec = { + type: 'vsphere', + url: url.value, + secret: { + name: secretName, + namespace, + }, + }; + + const vddkImage = vddkInitImage.value?.trim(); + + if (vddkImage) { + spec.settings = { vddkInitImage: vddkImage }; + } + const provider = await store.dispatch(`${ inStore }/create`, { type: HCI.FORKLIFT_PROVIDER, metadata: { name: providerName.value, namespace, }, - spec: { - type: 'vsphere', - url: url.value, - secret: { - name: secretName, - namespace, - }, - } + spec, }); await provider.save(); @@ -420,17 +443,28 @@ defineExpose({ testConnection, clickTestButton }); /> -
- -

- {{ t('harvester.addons.vmMigration.configureProvider.urlHint') }} -

+
+
+ +

+ {{ t('harvester.addons.vmMigration.configureProvider.urlHint') }} +

+
+
+ +
diff --git a/pkg/harvester/l10n/en-us.yaml b/pkg/harvester/l10n/en-us.yaml index 6cc9d993..5e2e4417 100644 --- a/pkg/harvester/l10n/en-us.yaml +++ b/pkg/harvester/l10n/en-us.yaml @@ -1898,6 +1898,9 @@ harvester: urlLabel: vCenter/ESXi URL urlPlaceholder: "https://vcenter.example.com/sdk" urlHint: Enter the full URL including https:// + vddkImageLabel: VMware VDDK Image + vddkImagePlaceholder: "[your-private-registry]/vddk-image:latest" + vddkImageTooltip: Specify the VMware VDDK (Virtual Disk Development Kit) Image to accelerate and optimize VMware virtual machine migration. skipSsl: Skip SSL certificate verification skipSslHint: Not recommended for production environments testConnection: diff --git a/pkg/harvester/pages/c/_cluster/vm-migration/provider-wizard.vue b/pkg/harvester/pages/c/_cluster/vm-migration/provider-wizard.vue index 29f9ffd8..196453c4 100644 --- a/pkg/harvester/pages/c/_cluster/vm-migration/provider-wizard.vue +++ b/pkg/harvester/pages/c/_cluster/vm-migration/provider-wizard.vue @@ -46,6 +46,7 @@ const stepData = reactive({ username: '', password: '', skipTlsVerify: false, + vddkInitImage: '', testPassed: false, testResult: null, testError: null, @@ -193,6 +194,7 @@ const init = async() => { stepData.provider.selectedProvider = fetchedProvider.metadata.name; stepData.provider.providerName = fetchedProvider.metadata.name; stepData.provider.url = fetchedProvider.spec?.url || ''; + stepData.provider.vddkInitImage = fetchedProvider.spec?.settings?.vddkInitImage || ''; stepData.provider.createdProvider = fetchedProvider; const secretRef = fetchedProvider.spec?.secret; diff --git a/pkg/harvester/pages/c/_cluster/vm-migration/vm-migration-wizard.vue b/pkg/harvester/pages/c/_cluster/vm-migration/vm-migration-wizard.vue index 0a5c9f3a..7e2ffb7d 100644 --- a/pkg/harvester/pages/c/_cluster/vm-migration/vm-migration-wizard.vue +++ b/pkg/harvester/pages/c/_cluster/vm-migration/vm-migration-wizard.vue @@ -41,6 +41,7 @@ const stepData = reactive({ username: '', password: '', skipTlsVerify: false, + vddkInitImage: '', testPassed: false, testResult: null, testError: null,