diff --git a/src/utils/components/ExportModal/ExportModal.tsx b/src/utils/components/ExportModal/ExportModal.tsx index 45dd2fc51..82c40089e 100644 --- a/src/utils/components/ExportModal/ExportModal.tsx +++ b/src/utils/components/ExportModal/ExportModal.tsx @@ -3,6 +3,7 @@ import React, { FC, useState } from 'react'; import { IoK8sApiCoreV1Pod } from '@kubevirt-ui/kubevirt-api/kubernetes'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; import { modelToGroupVersionKind, PodModel } from '@kubevirt-utils/models'; +import { createSecret } from '@kubevirt-utils/resources/secret/utils'; import { getName, getNamespace } from '@kubevirt-utils/resources/shared'; import { getRandomChars } from '@kubevirt-utils/utils/utils'; import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; @@ -19,7 +20,7 @@ import TabModal from '../TabModal/TabModal'; import { ALREADY_CREATED_ERROR_CODE } from './constants'; import ShowProgress from './ShowProgress'; -import { createSecret, createServiceAccount, createUploaderPod, exportInProgress } from './utils'; +import { createServiceAccount, createUploaderPod, exportInProgress } from './utils'; import ViewPodLogLink from './ViewPodLogLink'; import './export-modal.scss'; diff --git a/src/utils/components/ExportModal/utils.ts b/src/utils/components/ExportModal/utils.ts index 9730e235b..2c4cd85e4 100644 --- a/src/utils/components/ExportModal/utils.ts +++ b/src/utils/components/ExportModal/utils.ts @@ -1,12 +1,5 @@ -import { IoK8sApiCoreV1Pod, IoK8sApiCoreV1Secret } from '@kubevirt-ui/kubevirt-api/kubernetes'; -import { - PodModel, - RoleBindingModel, - RoleModel, - SecretModel, - ServiceAccountModel, -} from '@kubevirt-utils/models'; -import { encodeSecretKey } from '@kubevirt-utils/resources/secret/utils'; +import { IoK8sApiCoreV1Pod } from '@kubevirt-ui/kubevirt-api/kubernetes'; +import { PodModel, RoleBindingModel, RoleModel, ServiceAccountModel } from '@kubevirt-utils/models'; import { getRandomChars, isEmpty, isUpstream } from '@kubevirt-utils/utils/utils'; import { k8sCreate } from '@openshift-console/dynamic-plugin-sdk'; @@ -19,32 +12,6 @@ import { UPSTREAM_UPLOADER_IMAGE, } from './constants'; -type CreateSecretType = (input: { - namespace: string; - password: string; - secretName: string; - username: string; -}) => Promise; - -export const createSecret: CreateSecretType = ({ namespace, password, secretName, username }) => - k8sCreate({ - data: { - apiVersion: 'v1', - data: { - accessKeyId: encodeSecretKey(username), - secretKey: encodeSecretKey(password), - }, - kind: 'Secret', - metadata: { - name: secretName, - namespace, - }, - type: 'Opaque', - }, - model: SecretModel, - ns: namespace, - }); - export const createServiceAccount = async (namespace: string) => { await Promise.all([ k8sCreate({ data: serviceAccount, model: ServiceAccountModel, ns: namespace }), diff --git a/src/utils/components/FormPasswordInput/FormPasswordInput.tsx b/src/utils/components/FormPasswordInput/FormPasswordInput.tsx new file mode 100644 index 000000000..861159d51 --- /dev/null +++ b/src/utils/components/FormPasswordInput/FormPasswordInput.tsx @@ -0,0 +1,36 @@ +import React, { ComponentProps, forwardRef, HTMLProps, useState } from 'react'; + +import { Button, ButtonVariant, Split, TextInput, TextInputProps } from '@patternfly/react-core'; +import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons'; + +// PatternFly changed the signature of the 'onChange' handler for input elements. +// This causes issues with React Hook Form as it expects the default signature for an input element. +// So we have to create this wrapper component that takes care of converting these signatures for us. + +export type FormPasswordInputProps = Omit, 'onChange'> & + Pick, 'onChange'>; + +export const FormPasswordInput = forwardRef( + ({ onChange, ...props }, ref) => { + const [passwordHidden, setPasswordHidden] = useState(true); + const onChangeForward: TextInputProps['onChange'] = (event) => onChange?.(event); + + return ( + + onChangeForward(event, _value)} + ref={ref} + type={passwordHidden ? 'password' : 'text'} + /> + + + ); + }, +); + +// We need to fake the displayName to match what PatternFly expects. +// This is because PatternFly uses it to filter children in certain aspects. +FormPasswordInput.displayName = 'TextInput'; diff --git a/src/utils/resources/secret/utils.ts b/src/utils/resources/secret/utils.ts index 7aa294de3..951fcc0ab 100644 --- a/src/utils/resources/secret/utils.ts +++ b/src/utils/resources/secret/utils.ts @@ -1,13 +1,14 @@ import { Buffer } from 'buffer'; -import { SecretModel } from '@kubevirt-ui/kubevirt-api/console'; import { IoK8sApiCoreV1Secret } from '@kubevirt-ui/kubevirt-api/kubernetes'; import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt'; import { SecretSelectionOption, SSHSecretDetails, } from '@kubevirt-utils/components/SSHSecretModal/utils/types'; +import { SecretModel } from '@kubevirt-utils/models'; import { isEmpty } from '@kubevirt-utils/utils/utils'; +import { k8sCreate } from '@openshift-console/dynamic-plugin-sdk'; import { getName } from '../shared'; @@ -67,3 +68,29 @@ export const getInitialSSHDetails = ({ sshSecretName: sshSecretName || '', sshSecretNamespace: '', }; + +type CreateSecretType = (input: { + namespace: string; + password: string; + secretName: string; + username: string; +}) => Promise; + +export const createSecret: CreateSecretType = ({ namespace, password, secretName, username }) => + k8sCreate({ + data: { + apiVersion: 'v1', + data: { + accessKeyId: encodeSecretKey(username), + secretKey: encodeSecretKey(password), + }, + kind: 'Secret', + metadata: { + name: secretName, + namespace, + }, + type: 'Opaque', + }, + model: SecretModel, + ns: namespace, + }); diff --git a/src/utils/resources/template/utils/helpers.ts b/src/utils/resources/template/utils/helpers.ts index d8009cdb2..c9a0e04fe 100644 --- a/src/utils/resources/template/utils/helpers.ts +++ b/src/utils/resources/template/utils/helpers.ts @@ -4,6 +4,7 @@ import { TemplateParameter, V1Template } from '@kubevirt-ui/kubevirt-api/console import VirtualMachineModel from '@kubevirt-ui/kubevirt-api/console/models/VirtualMachineModel'; import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt'; import { getAnnotation } from '@kubevirt-utils/resources/shared'; +import { getRootDataVolumeTemplateSpec } from '@kubevirt-utils/resources/vm'; import { generatePrettyName } from '@kubevirt-utils/utils/utils'; import { ANNOTATIONS } from './annotations'; @@ -12,7 +13,7 @@ import { TEMPLATE_TYPE_BASE, TEMPLATE_TYPE_LABEL, } from './constants'; -import { getTemplatePVCName } from './selectors'; +import { getTemplatePVCName, getTemplateVirtualMachineObject } from './selectors'; // Only used for replacing parameters in the template, do not use for anything else // eslint-disable-next-line require-jsdoc @@ -70,3 +71,9 @@ export const generateParamsWithPrettyName = (template: V1Template) => { } return []; }; + +export const bootDiskSourceIsRegistry = (template: V1Template) => { + const vmObject: V1VirtualMachine = getTemplateVirtualMachineObject(template); + const rootDataVolumeTemplateSpec = getRootDataVolumeTemplateSpec(vmObject); + return Boolean(rootDataVolumeTemplateSpec?.spec?.source?.registry); +}; diff --git a/src/utils/resources/vm/utils/selectors.ts b/src/utils/resources/vm/utils/selectors.ts index 9efc9ffd6..13df70c70 100644 --- a/src/utils/resources/vm/utils/selectors.ts +++ b/src/utils/resources/vm/utils/selectors.ts @@ -2,6 +2,7 @@ import { V1AccessCredential, V1Bootloader, V1CPU, + V1DataVolumeTemplateSpec, V1Devices, V1Disk, V1DomainSpec, @@ -79,6 +80,17 @@ export const getVolumeSnapshotStatuses = (vm: V1VirtualMachine) => */ export const getDataVolumeTemplates = (vm: V1VirtualMachine) => vm?.spec?.dataVolumeTemplates || []; +/** + * A selector for the virtual machine's root data volume + * @param {V1VirtualMachine} vm the virtual machine + * @returns the virtual machine's root data volume + */ +export const getRootDataVolumeTemplateSpec = (vm: V1VirtualMachine): V1DataVolumeTemplateSpec => { + const volume = getVolumes(vm)?.find((v) => v.name === ROOTDISK); + + return vm.spec.dataVolumeTemplates?.find((dv) => dv.metadata.name === volume?.dataVolume?.name); +}; + /** * A selector for the virtual machine's config maps * @param {V1VirtualMachine} vm the virtual machine diff --git a/src/utils/utils/utils.ts b/src/utils/utils/utils.ts index 8246c1fc0..a8b653f5c 100644 --- a/src/utils/utils/utils.ts +++ b/src/utils/utils/utils.ts @@ -53,6 +53,8 @@ export const getRandomChars = (len = 6): string => { .substr(1, len); }; +export const addRandomSuffix = (str: string) => str.concat(`-${getRandomChars()}`); + export const SSH_PUBLIC_KEY_VALIDATION_REGEX = /^(sk-)?(ssh-rsa AAAAB3NzaC1yc2|ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNT|ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzOD|ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1Mj|ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|ssh-dss AAAAB3NzaC1kc3)[0-9A-Za-z+/]+[=]{0,3}( .*)?$/; @@ -152,5 +154,6 @@ export const appendDockerPrefix = (image: string) => { return image?.startsWith(DOCKER_PREFIX) ? image : DOCKER_PREFIX.concat(image); }; export const removeDockerPrefix = (image: string) => image?.replace(DOCKER_PREFIX, ''); + export const isAllNamespaces = (namespace: string) => !namespace || namespace === ALL_NAMESPACES || namespace === ALL_NAMESPACES_SESSION_KEY; diff --git a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/CustomizeSource/SelectSource.tsx b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/CustomizeSource/SelectSource.tsx index fb01fac4f..cced8c0ea 100644 --- a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/CustomizeSource/SelectSource.tsx +++ b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/CustomizeSource/SelectSource.tsx @@ -7,6 +7,7 @@ import React, { useRef, } from 'react'; +import { useDrawerContext } from '@catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/useDrawerContext'; import { V1beta1DataVolumeSpec, V1ContainerDiskSource } from '@kubevirt-ui/kubevirt-api/kubevirt'; import CapacityInput from '@kubevirt-utils/components/CapacityInput/CapacityInput'; import { DataUpload } from '@kubevirt-utils/hooks/useCDIUpload/useCDIUpload'; @@ -66,6 +67,7 @@ export const SelectSource: FC = ({ }) => { const { t } = useKubevirtTranslation(); const initialDiskSource = useRef(selectedSource); + const { registryCredentials, setRegistryCredentials } = useDrawerContext(); const volumeQuantity = getQuantityFromSource(selectedSource as V1beta1DataVolumeSpec); @@ -182,8 +184,10 @@ export const SelectSource: FC = ({ {[CONTAINER_DISK_SOURCE_NAME, REGISTRY_SOURCE_NAME].includes(selectedSourceType) && ( )} diff --git a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/CustomizeSource/Sources/ContainerSource.tsx b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/CustomizeSource/Sources/ContainerSource.tsx index f3c0e35c4..42968cf8a 100644 --- a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/CustomizeSource/Sources/ContainerSource.tsx +++ b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/CustomizeSource/Sources/ContainerSource.tsx @@ -1,22 +1,27 @@ -import React, { FC, FormEventHandler } from 'react'; +import React, { Dispatch, FC, FormEventHandler, SetStateAction } from 'react'; import { useFormContext } from 'react-hook-form'; import FormGroupHelperText from '@kubevirt-utils/components/FormGroupHelperText/FormGroupHelperText'; +import { FormPasswordInput } from '@kubevirt-utils/components/FormPasswordInput/FormPasswordInput'; import { FormTextInput } from '@kubevirt-utils/components/FormTextInput/FormTextInput'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; import { FormGroup, ValidatedOptions } from '@patternfly/react-core'; type ContainerSourceProps = { onInputValueChange: FormEventHandler; + registryCredentials: { password: string; username: string }; registrySourceHelperText: string; selectedSourceType: string; + setRegistryCredentials: Dispatch>; testId: string; }; const ContainerSource: FC = ({ onInputValueChange, + registryCredentials, registrySourceHelperText, selectedSourceType, + setRegistryCredentials, testId, }) => { const { t } = useKubevirtTranslation(); @@ -29,28 +34,64 @@ const ContainerSource: FC = ({ ? ValidatedOptions.error : ValidatedOptions.default; + const handleCredentialsChange = (e, field: 'password' | 'username') => { + setRegistryCredentials({ ...registryCredentials, [field]: e.target.value }); + }; + return ( - - - - {errors?.[`${testId}-containerImage`] - ? t('This field is required') - : registrySourceHelperText} - - + <> + + + + {errors?.[`${testId}-containerImage`] + ? t('This field is required') + : registrySourceHelperText} + + + + handleCredentialsChange(e, 'username')} + type="text" + validated={validated} + /> + + + handleCredentialsChange(e, 'password')} + type="text" + validated={validated} + /> + + ); }; diff --git a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/utils.ts b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/utils.ts index 8295339fa..c90f16f82 100644 --- a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/utils.ts +++ b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/StorageSection/utils.ts @@ -11,11 +11,9 @@ import { ROOTDISK } from '@kubevirt-utils/constants/constants'; import { CDI_BIND_REQUESTED_ANNOTATION } from '@kubevirt-utils/hooks/useCDIUpload/consts'; import { t } from '@kubevirt-utils/hooks/useKubevirtTranslation'; import { getTemplateContainerDisks } from '@kubevirt-utils/resources/template'; -import { getDisks, getVolumes } from '@kubevirt-utils/resources/vm'; +import { getDisks, getRootDataVolumeTemplateSpec, getVolumes } from '@kubevirt-utils/resources/vm'; import { isEmpty } from '@kubevirt-utils/utils/utils'; -import { getRootDataVolume } from '../utils'; - export const getRegistryHelperText = (template: V1Template) => { const containerDisks = getTemplateContainerDisks(template); @@ -68,7 +66,7 @@ export const overrideVirtualMachineDataVolumeSpec = ( customSource?: V1beta1DataVolumeSpec, ): V1VirtualMachine => { return produceVMDisks(virtualMachine, (draftVM) => { - const rootDataVolume = getRootDataVolume(draftVM); + const rootDataVolume = getRootDataVolumeTemplateSpec(draftVM); if (isEmpty(customSource)) return; diff --git a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/constants.ts b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/constants.ts index 49206ad5e..1eeeda2c3 100644 --- a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/constants.ts +++ b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/constants.ts @@ -7,8 +7,10 @@ export const initialValue: DrawerContext = { diskFile: null, diskUpload: null, isBootSourceAvailable: null, + registryCredentials: { password: '', username: '' }, setCDFile: null, setDiskFile: null, + setRegistryCredentials: null, setSSHDetails: null, setStorageClassName: null, setTemplate: null, diff --git a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/useCreateDrawerForm.tsx b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/useCreateDrawerForm.tsx index 70fe8f5f6..2eef20029 100644 --- a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/useCreateDrawerForm.tsx +++ b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/useCreateDrawerForm.tsx @@ -36,9 +36,11 @@ import { DISABLED_GUEST_SYSTEM_LOGS_ACCESS } from '@kubevirt-utils/hooks/useFeat import { useFeatures } from '@kubevirt-utils/hooks/useFeatures/useFeatures'; import useKubevirtUserSettings from '@kubevirt-utils/hooks/useKubevirtUserSettings/useKubevirtUserSettings'; import { RHELAutomaticSubscriptionData } from '@kubevirt-utils/hooks/useRHELAutomaticSubscription/utils/types'; -import { getAnnotation, getLabel, getResourceUrl } from '@kubevirt-utils/resources/shared'; +import { createSecret } from '@kubevirt-utils/resources/secret/utils'; +import { getAnnotation, getLabel, getName, getResourceUrl } from '@kubevirt-utils/resources/shared'; import { ANNOTATIONS, + bootDiskSourceIsRegistry, getTemplateOS, getTemplateVirtualMachineObject, LABEL_USED_TEMPLATE_NAME, @@ -49,7 +51,7 @@ import { HEADLESS_SERVICE_LABEL, HEADLESS_SERVICE_NAME, } from '@kubevirt-utils/utils/headless-service'; -import { ensurePath, isEmpty } from '@kubevirt-utils/utils/utils'; +import { addRandomSuffix, ensurePath, isEmpty } from '@kubevirt-utils/utils/utils'; import { k8sCreate, useAccessReview, useK8sModels } from '@openshift-console/dynamic-plugin-sdk'; import { VM_FOLDER_LABEL } from '@virtualmachines/tree/utils/constants'; @@ -76,6 +78,7 @@ const useCreateDrawerForm = ( cdFile, diskFile, isBootSourceAvailable, + registryCredentials, setVM, sshDetails, storageClassName, @@ -85,6 +88,7 @@ const useCreateDrawerForm = ( uploadDiskData, vm, } = useDrawerContext(); + const { password, username } = registryCredentials; const { nameField, onVMNameChange } = useCreateVMName(); @@ -107,6 +111,18 @@ const useCreateDrawerForm = ( setIsQuickCreating(true); setCreateError(undefined); + const addSecret = username && password && bootDiskSourceIsRegistry(template); + const imageSecretName = addRandomSuffix(getName(template)); + + if (addSecret) { + await createSecret({ + namespace, + password, + secretName: imageSecretName, + username, + }); + } + const templateToProcess = produce(template, (draftTemplate) => { const vmObject = getTemplateVirtualMachineObject(draftTemplate); @@ -120,6 +136,9 @@ const useCreateDrawerForm = ( vmObject.spec.template.spec.domain.cpu.cores = cpu?.cores; vmObject.spec.template.spec.domain.memory.guest = memory; + if (addSecret) + vmObject.spec.dataVolumeTemplates[0].spec.source.registry.secretRef = imageSecretName; + if (!getLabels(vmObject.spec.template)) vmObject.spec.template.metadata.labels = {}; vmObject.spec.template.metadata.labels[HEADLESS_SERVICE_LABEL] = HEADLESS_SERVICE_NAME; diff --git a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/useDrawerContext.tsx b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/useDrawerContext.tsx index a51023b72..bc6a3c46e 100644 --- a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/useDrawerContext.tsx +++ b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/hooks/useDrawerContext.tsx @@ -46,8 +46,10 @@ export type DrawerContext = { diskFile: File | string; diskUpload: DataUpload; isBootSourceAvailable: boolean; + registryCredentials: { password: string; username: string }; setCDFile: (file: File | string) => void; setDiskFile: (file: File | string) => void; + setRegistryCredentials: (credentials: { password: string; username: string }) => void; setSSHDetails: (details: SSHSecretDetails) => void; setStorageClassName: (scName: string) => void; setTemplate: Updater; @@ -75,6 +77,8 @@ const useDrawer = (template: V1Template) => { const [cdFile, setCDFile] = useState(); const [storageClassName, setStorageClassName] = useState(null); + const [registryCredentials, setRegistryCredentials] = useState({ password: '', username: '' }); + const [templateWithGeneratedParams, loading, error] = useVMTemplateGeneratedParams(template); const [{ clusterDefaultStorageClass, virtDefaultStorageClass }] = useDefaultStorageClass(); @@ -124,8 +128,10 @@ const useDrawer = (template: V1Template) => { diskFile, diskUpload, isBootSourceAvailable: isDefaultDiskSource ? isBootSourceAvailable : true, + registryCredentials, setCDFile, setDiskFile, + setRegistryCredentials, setSSHDetails, setStorageClassName, setTemplate: setCustomizedTemplate, diff --git a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/utils.ts b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/utils.ts index 17fb2a5f8..e5e09f1e4 100644 --- a/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/utils.ts +++ b/src/views/catalog/templatescatalog/components/TemplatesCatalogDrawer/utils.ts @@ -11,13 +11,12 @@ import { DEFAULT_CDROM_DISK_SIZE, DEFAULT_DISK_SIZE, } from '@kubevirt-utils/components/DiskModal/utils/constants'; -import { ROOTDISK } from '@kubevirt-utils/constants/constants'; import { UploadDataProps } from '@kubevirt-utils/hooks/useCDIUpload/useCDIUpload'; import { getTemplateParameterValue, getTemplateVirtualMachineObject, } from '@kubevirt-utils/resources/template'; -import { getVolumes } from '@kubevirt-utils/resources/vm'; +import { getRootDataVolumeTemplateSpec, getVolumes } from '@kubevirt-utils/resources/vm'; import { ensurePath, isEmpty, removeDockerPrefix } from '@kubevirt-utils/utils/utils'; import { INSTALLATION_CDROM_NAME } from './StorageSection/constants'; @@ -81,12 +80,6 @@ export const changeTemplateParameterValue = ( return template; }; -export const getRootDataVolume = (vm: V1VirtualMachine) => { - const volume = getVolumes(vm)?.find((v) => v.name === ROOTDISK); - - return vm.spec.dataVolumeTemplates?.find((dv) => dv.metadata.name === volume?.dataVolume?.name); -}; - export const getUploadDataVolume = ( name: string, namespace: string, @@ -175,7 +168,7 @@ export const uploadFiles: UploadFiles = ({ uploadDiskData, vm, }) => { - const dataVolumeTemplate = getRootDataVolume(vm); + const dataVolumeTemplate = getRootDataVolumeTemplateSpec(vm); const diskDVName = dataVolumeTemplate?.metadata?.name; const cdDVName = `${vm?.metadata?.name}-${INSTALLATION_CDROM_NAME}`;