Skip to content

Commit

Permalink
Merge pull request #2339 from pcbailey/bug-registry-credentials-dont-…
Browse files Browse the repository at this point in the history
…persist-in-customize-flow

CNV-53248: Add registry credentials to the template wizard flow
  • Loading branch information
openshift-merge-bot[bot] authored Jan 23, 2025
2 parents 0cf2714 + 9284ad6 commit b136684
Show file tree
Hide file tree
Showing 26 changed files with 361 additions and 90 deletions.
6 changes: 5 additions & 1 deletion src/utils/components/DiskModal/ContainerDiskModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ const ContainerDiskModal: FC<V1SubDiskModalProps> = ({
<Form>
<BootSourceCheckbox editDiskName={editDiskName} isDisabled={isVMRunning} vm={vm} />
<DiskNameInput />
<DiskSourceContainer fieldName={CONTAINERDISK_IMAGE_FIELD} os={os} />
<DiskSourceContainer
fieldName={CONTAINERDISK_IMAGE_FIELD}
isEphemeralDiskSource
os={os}
/>
<DynamicSize />
<DiskTypeSelect isVMRunning={isVMRunning} />
<DiskInterfaceSelect isVMRunning={isVMRunning} />
Expand Down
2 changes: 2 additions & 0 deletions src/utils/components/DiskModal/DiskModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { V1DiskModalProps } from './utils/types';
const DiskModal: FC<V1DiskModalProps> = ({
createDiskSource,
createdPVCName,
defaultFormValues,
editDiskName,
isOpen,
onClose,
Expand All @@ -34,6 +35,7 @@ const DiskModal: FC<V1DiskModalProps> = ({
return (
<Modal
createDiskSource={createDiskSource}
defaultFormValues={defaultFormValues}
editDiskName={editDiskName}
isCreated={!isEmpty(createdPVCName)}
isOpen={isOpen}
Expand Down
70 changes: 38 additions & 32 deletions src/utils/components/DiskModal/RegistryDiskModal.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,72 @@
import React, { FC } from 'react';
import { FormProvider, useForm } from 'react-hook-form';

import AdvancedSettings from '@kubevirt-utils/components/DiskModal/components/AdvancedSettings/AdvancedSettings';
import BootSourceCheckbox from '@kubevirt-utils/components/DiskModal/components/BootSourceCheckbox/BootSourceCheckbox';
import DiskInterfaceSelect from '@kubevirt-utils/components/DiskModal/components/DiskInterfaceSelect/DiskInterfaceSelect';
import DiskNameInput from '@kubevirt-utils/components/DiskModal/components/DiskNameInput/DiskNameInput';
import DiskSizeInput from '@kubevirt-utils/components/DiskModal/components/DiskSizeInput/DiskSizeInput';
import DiskSourceContainer from '@kubevirt-utils/components/DiskModal/components/DiskSourceSelect/components/DiskSourceContainer/DiskSourceContainer';
import DiskTypeSelect from '@kubevirt-utils/components/DiskModal/components/DiskTypeSelect/DiskTypeSelect';
import PendingChanges from '@kubevirt-utils/components/DiskModal/components/PendingChanges';
import StorageClassAndPreallocation from '@kubevirt-utils/components/DiskModal/components/StorageClassAndPreallocation/StorageClassAndPreallocation';
import {
REGISTRY_CREDENTIALS_FIELD,
REGISTRYURL_DATAVOLUME_FIELD,
} from '@kubevirt-utils/components/DiskModal/components/utils/constants';
import { diskModalTitle, getOS } from '@kubevirt-utils/components/DiskModal/utils/helpers';
import { submit } from '@kubevirt-utils/components/DiskModal/utils/submit';
import TabModal from '@kubevirt-utils/components/TabModal/TabModal';
import { getNamespace } from '@kubevirt-utils/resources/shared';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import { Form } from '@patternfly/react-core';
import { isRunning } from '@virtualmachines/utils';

import TabModal from '../TabModal/TabModal';

import AdvancedSettings from './components/AdvancedSettings/AdvancedSettings';
import BootSourceCheckbox from './components/BootSourceCheckbox/BootSourceCheckbox';
import DiskInterfaceSelect from './components/DiskInterfaceSelect/DiskInterfaceSelect';
import DiskNameInput from './components/DiskNameInput/DiskNameInput';
import DiskSizeInput from './components/DiskSizeInput/DiskSizeInput';
import DiskSourceContainer from './components/DiskSourceSelect/components/DiskSourceContainer/DiskSourceContainer';
import DiskTypeSelect from './components/DiskTypeSelect/DiskTypeSelect';
import PendingChanges from './components/PendingChanges';
import StorageClassAndPreallocation from './components/StorageClassAndPreallocation/StorageClassAndPreallocation';
import { REGISTRYURL_DATAVOLUME_FIELD } from './components/utils/constants';
import { getDefaultCreateValues, getDefaultEditValues } from './utils/form';
import { diskModalTitle, getOS } from './utils/helpers';
import { submit } from './utils/submit';
import { SourceTypes, V1DiskFormState, V1SubDiskModalProps } from './utils/types';

const RegistryDiskModal: FC<V1SubDiskModalProps> = ({
editDiskName,
isCreated,
isOpen,
onClose,
onSubmit,
pvc,
vm,
}) => {
const os = getOS(vm);
const isVMRunning = isRunning(vm);
const RegistryDiskModal: FC<V1SubDiskModalProps> = (props) => {
const { defaultFormValues, editDiskName, isCreated, isOpen, onClose, onSubmit, pvc, vm } = props;

const isEditDisk = !isEmpty(editDiskName);
const namespace = getNamespace(vm);

const defaultValues = isEditDisk
? getDefaultEditValues(vm, editDiskName, defaultFormValues)
: getDefaultCreateValues(vm, SourceTypes.REGISTRY);

const methods = useForm<V1DiskFormState>({
defaultValues: isEditDisk
? getDefaultEditValues(vm, editDiskName)
: getDefaultCreateValues(vm, SourceTypes.REGISTRY),
defaultValues,
mode: 'all',
});

const {
formState: { isSubmitting, isValid },
handleSubmit,
watch,
} = methods;

const formRegistryCredentials = watch(REGISTRY_CREDENTIALS_FIELD);
const { password, username } = formRegistryCredentials || { password: '', username: '' };
const credentialsValid = (username && password) || (!username && !password);

const handleSubmitForm = () => {
return handleSubmit(async (data) => submit({ data, editDiskName, onSubmit, pvc, vm }))();
};

const os = getOS(vm);
const isVMRunning = isRunning(vm);
const namespace = getNamespace(vm);

return (
<FormProvider {...methods}>
<TabModal
onSubmit={() =>
handleSubmit(async (data) => submit({ data, editDiskName, onSubmit, pvc, vm }))()
}
closeOnSubmit={isValid}
headerText={diskModalTitle(isEditDisk, isVMRunning)}
isDisabled={!credentialsValid}
isLoading={isSubmitting}
isOpen={isOpen}
onClose={onClose}
onSubmit={handleSubmitForm}
>
<PendingChanges isVMRunning={isVMRunning} />
<Form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,109 @@ import { FieldPath, useFormContext } from 'react-hook-form';

import { V1DiskFormState } from '@kubevirt-utils/components/DiskModal/utils/types';
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 { OS_NAME_TYPES } from '@kubevirt-utils/resources/template';
import { isUpstream } from '@kubevirt-utils/utils/utils';
import { FormGroup, TextInput, ValidatedOptions } from '@patternfly/react-core';
import { FormGroup, ValidatedOptions } from '@patternfly/react-core';

import { diskSourceEphemeralFieldID } from '../../utils/constants';
import { REGISTRY_PASSWORD_FIELD, REGISTRY_USERNAME_FIELD } from '../../../utils/constants';
import {
diskSourceEphemeralFieldID,
diskSourcePasswordFieldID,
diskSourceUsernameFieldID,
} from '../../utils/constants';

import { OS_REGISTERY_LINKS } from './utils/constants';

type DiskSourceUrlInputProps = {
fieldName: FieldPath<V1DiskFormState>;
isEphemeralDiskSource?: boolean;
os: string;
};

const DiskSourceContainer: FC<DiskSourceUrlInputProps> = ({ fieldName, os }) => {
const DiskSourceContainer: FC<DiskSourceUrlInputProps> = ({
fieldName,
isEphemeralDiskSource = false,
os,
}) => {
const { t } = useKubevirtTranslation();
const { getFieldState, register } = useFormContext<V1DiskFormState>();
const {
formState: { errors },
getFieldState,
register,
} = useFormContext<V1DiskFormState>();

const isRHELOS = os?.includes(OS_NAME_TYPES.rhel);
// we show feodra on upstream and rhel on downstream, and default as fedora if not exists.
// we show fedora on upstream and rhel on downstream, and default as fedora if not exists.
const exampleURL =
isRHELOS && isUpstream
? OS_REGISTERY_LINKS.fedora
: OS_REGISTERY_LINKS[os] || OS_REGISTERY_LINKS.fedora;

const { error } = getFieldState(fieldName);

return (
<FormGroup fieldId={diskSourceEphemeralFieldID} isRequired label={t('Container')}>
<TextInput
data-test-id={diskSourceEphemeralFieldID}
id={diskSourceEphemeralFieldID}
{...register(fieldName, { required: t('Container is required.') })}
/>
<FormGroupHelperText validated={error ? ValidatedOptions.error : ValidatedOptions.default}>
{error ? (
error?.message
) : (
<>
{t('Example: ')}
{exampleURL}
</>
)}
</FormGroupHelperText>
</FormGroup>
<>
<FormGroup fieldId={diskSourceEphemeralFieldID} isRequired label={t('Container')}>
<FormTextInput
data-test-id={diskSourceEphemeralFieldID}
id={diskSourceEphemeralFieldID}
{...register(fieldName, { required: t('Container is required.') })}
/>
<FormGroupHelperText validated={error ? ValidatedOptions.error : ValidatedOptions.default}>
{error ? (
error?.message
) : (
<>
{t('Example: ')}
{exampleURL}
</>
)}
</FormGroupHelperText>
</FormGroup>
{!isEphemeralDiskSource && (
<>
<FormGroup
className="disk-source-form-group"
fieldId={diskSourceUsernameFieldID}
label={t('Username')}
>
<FormTextInput
{...register(REGISTRY_USERNAME_FIELD)}
validated={
errors?.[REGISTRY_USERNAME_FIELD]
? ValidatedOptions.error
: ValidatedOptions.default
}
aria-label={t('Username')}
data-test-id={diskSourceUsernameFieldID}
id={diskSourceUsernameFieldID}
type="text"
/>
</FormGroup>
<FormGroup
className="disk-source-form-group"
fieldId={diskSourcePasswordFieldID}
label={t('Password')}
>
<FormPasswordInput
{...register(REGISTRY_PASSWORD_FIELD)}
validated={
errors?.[REGISTRY_PASSWORD_FIELD]
? ValidatedOptions.error
: ValidatedOptions.default
}
aria-label={t('Password')}
data-test-id={diskSourcePasswordFieldID}
id={diskSourcePasswordFieldID}
type="text"
/>
</FormGroup>
</>
)}
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const diskSourceURLFieldID = 'disk-source-url';
export const ephemeralDiskSizeFieldID = 'ephemeral-disk-size';
export const diskSourceFieldID = 'disk-source';
export const diskSourceEphemeralFieldID = 'disk-source-container';
export const diskSourceUsernameFieldID = 'disk-source-username';
export const diskSourcePasswordFieldID = 'disk-source-password';

export const optionLabelMapper: { [key in SourceTypes]: string } = {
[SourceTypes.BLANK]: t('Empty disk (blank)'),
Expand Down
4 changes: 4 additions & 0 deletions src/utils/components/DiskModal/components/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ export const CONTAINERDISK_IMAGE_FIELD = 'volume.containerDisk.image';
export const PVC_CLAIMNAME_FIELD = 'volume.persistentVolumeClaim.claimName';

export const REGISTRYURL_DATAVOLUME_FIELD = 'dataVolumeTemplate.spec.source.registry.url';

export const REGISTRY_CREDENTIALS_FIELD = 'registryCredentials';
export const REGISTRY_USERNAME_FIELD = 'registryCredentials.username';
export const REGISTRY_PASSWORD_FIELD = 'registryCredentials.password';
9 changes: 7 additions & 2 deletions src/utils/components/DiskModal/utils/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { isRunning } from '@virtualmachines/utils';

import { DEFAULT_DISK_SIZE } from './constants';
import { doesSourceRequireDataVolume, getDefaultDiskType } from './helpers';
import { SourceTypes, V1DiskFormState } from './types';
import { DefaultFormValues, SourceTypes, V1DiskFormState } from './types';

const getDefaultDataVolumeTemplate = (name: string): V1DataVolumeTemplateSpec => ({
metadata: { name },
Expand Down Expand Up @@ -46,7 +46,11 @@ const createInitialStateFromSource: Record<
(dataVolumeTemplate.spec.source.snapshot = { name: '', namespace: '' }),
};

export const getDefaultEditValues = (vm: V1VirtualMachine, editDiskName?: string) => {
export const getDefaultEditValues = (
vm: V1VirtualMachine,
editDiskName?: string,
defaultValues?: DefaultFormValues,
) => {
const isBootSource = getBootDisk(vm)?.name === editDiskName;
let diskToEdit = getDisks(vm)?.find((disk) => disk.name === editDiskName);
const volumeToEdit = getVolumes(vm)?.find((volume) => volume.name === editDiskName);
Expand All @@ -61,6 +65,7 @@ export const getDefaultEditValues = (vm: V1VirtualMachine, editDiskName?: string
disk: diskToEdit,
isBootSource,
volume: volumeToEdit,
...(defaultValues ? defaultValues : {}),
};
};

Expand Down
7 changes: 5 additions & 2 deletions src/utils/components/DiskModal/utils/submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ export const addDisk = (data: V1DiskFormState, vm: V1VirtualMachine) => {
type SubmitInput = {
data: V1DiskFormState;
editDiskName: string;
onSubmit: (updatedVM: V1VirtualMachine) => Promise<V1VirtualMachine | void>;
onSubmit: (
updatedVM: V1VirtualMachine,
diskFormState?: V1DiskFormState,
) => Promise<V1VirtualMachine | void>;
pvc?: IoK8sApiCoreV1PersistentVolumeClaim;
vm: V1VirtualMachine;
};
Expand Down Expand Up @@ -145,5 +148,5 @@ export const submit = async ({ data, editDiskName, onSubmit, pvc, vm }: SubmitIn
});
}

return shouldHotplug ? (hotplugPromise(newVM, data) as Promise<any>) : onSubmit(newVM);
return shouldHotplug ? (hotplugPromise(newVM, data) as Promise<any>) : onSubmit(newVM, data);
};
33 changes: 21 additions & 12 deletions src/utils/components/DiskModal/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { RegistryCredentials } from '@catalog/utils/useRegistryCredentials/utils/types';
import { V1beta1DataVolume } from '@kubevirt-ui/kubevirt-api/containerized-data-importer/models';
import { IoK8sApiCoreV1PersistentVolumeClaim } from '@kubevirt-ui/kubevirt-api/kubernetes';
import {
Expand Down Expand Up @@ -37,29 +38,37 @@ export enum VolumeTypes {
SERVICE_ACCOUNT = 'serviceAccount',
}

export type V1DiskFormState = {
dataVolumeTemplate?: V1DataVolumeTemplateSpec;
disk: V1Disk;
expandPVCSize?: string;
isBootSource: boolean;
registryCredentials?: RegistryCredentials;
storageClassProvisioner?: string;
storageProfileSettingsApplied?: boolean;
uploadFile?: { file: File; filename: string };
volume: V1Volume;
};

export type DefaultFormValues = Partial<V1DiskFormState>;

export type V1DiskModalProps = {
createDiskSource?: SourceTypes;
createdPVCName?: string;
defaultFormValues?: DefaultFormValues;
editDiskName?: string;
isOpen: boolean;
onClose: () => void;
onSubmit: (updatedVM: V1VirtualMachine) => Promise<V1VirtualMachine | void>;
onSubmit: (
updatedVM: V1VirtualMachine,
diskFormState?: V1DiskFormState,
) => Promise<V1VirtualMachine | void>;
onUploadedDataVolume?: (dataVolume: V1beta1DataVolume) => void;
vm: V1VirtualMachine;
};

export type V1SubDiskModalProps = V1DiskModalProps & {
defaultFormValues?: DefaultFormValues;
isCreated: boolean;
pvc: IoK8sApiCoreV1PersistentVolumeClaim;
};

export type V1DiskFormState = {
dataVolumeTemplate?: V1DataVolumeTemplateSpec;
disk: V1Disk;
expandPVCSize?: string;
isBootSource: boolean;
storageClassProvisioner?: string;
storageProfileSettingsApplied?: boolean;
uploadFile?: { file: File; filename: string };
volume: V1Volume;
};
Loading

0 comments on commit b136684

Please sign in to comment.