Skip to content

Commit

Permalink
Add registry credentials to the template wizard flow
Browse files Browse the repository at this point in the history
  • Loading branch information
pcbailey committed Jan 7, 2025
1 parent 3abd848 commit e80c61c
Show file tree
Hide file tree
Showing 22 changed files with 311 additions and 84 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
25 changes: 14 additions & 11 deletions src/utils/components/DiskModal/DiskModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { FC } from 'react';

import { WizardVMContextProvider } from '@catalog/utils/WizardVMContext';
import { getName, getNamespace } from '@kubevirt-utils/resources/shared';
import { getDataVolumeTemplates, getVolumes } from '@kubevirt-utils/resources/vm';
import { isEmpty } from '@kubevirt-utils/utils/utils';
Expand Down Expand Up @@ -32,17 +33,19 @@ const DiskModal: FC<V1DiskModalProps> = ({
const Modal = modalsBySource[createDiskSource || editDiskSource];

return (
<Modal
createDiskSource={createDiskSource}
editDiskName={editDiskName}
isCreated={!isEmpty(createdPVCName)}
isOpen={isOpen}
onClose={onClose}
onSubmit={onSubmit}
onUploadedDataVolume={onUploadedDataVolume}
pvc={pvc}
vm={vm}
/>
<WizardVMContextProvider>
<Modal
createDiskSource={createDiskSource}
editDiskName={editDiskName}
isCreated={!isEmpty(createdPVCName)}
isOpen={isOpen}
onClose={onClose}
onSubmit={onSubmit}
onUploadedDataVolume={onUploadedDataVolume}
pvc={pvc}
vm={vm}
/>
</WizardVMContextProvider>
);
};

Expand Down
73 changes: 41 additions & 32 deletions src/utils/components/DiskModal/RegistryDiskModal.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,75 @@
import React, { FC } from 'react';
import { FormProvider, useForm } from 'react-hook-form';

import useRegistryCredentials from '@catalog/utils/useRegistryCredentials/useRegistryCredentials';
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 { editDiskName, isCreated, isOpen, onClose, onSubmit, pvc, vm } = props;
const { decodedRegistryCredentials, updateRegistryCredentials } = useRegistryCredentials();

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

const defaultValues = isEditDisk
? getDefaultEditValues(vm, editDiskName, decodedRegistryCredentials)
: 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;
const credentialsValid = (username && password) || (!username && !password);

const handleSubmitForm = () => {
updateRegistryCredentials(formRegistryCredentials);
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 { 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')}>
<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>
{!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';
8 changes: 7 additions & 1 deletion src/utils/components/DiskModal/utils/form.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { RegistryCredentials } from '@catalog/utils/useRegistryCredentials/utils/types';
import DataSourceModel from '@kubevirt-ui/kubevirt-api/console/models/DataSourceModel';
import {
V1DataVolumeTemplateSpec,
Expand Down Expand Up @@ -46,7 +47,11 @@ const createInitialStateFromSource: Record<
(dataVolumeTemplate.spec.source.snapshot = { name: '', namespace: '' }),
};

export const getDefaultEditValues = (vm: V1VirtualMachine, editDiskName?: string) => {
export const getDefaultEditValues = (
vm: V1VirtualMachine,
editDiskName?: string,
registryCredentials?: RegistryCredentials,
) => {
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 @@ -60,6 +65,7 @@ export const getDefaultEditValues = (vm: V1VirtualMachine, editDiskName?: string
dataVolumeTemplate,
disk: diskToEdit,
isBootSource,
registryCredentials,
volume: volumeToEdit,
};
};
Expand Down
2 changes: 2 additions & 0 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 @@ -58,6 +59,7 @@ export type V1DiskFormState = {
disk: V1Disk;
expandPVCSize?: string;
isBootSource: boolean;
registryCredentials?: RegistryCredentials;
storageClassProvisioner?: string;
storageProfileSettingsApplied?: boolean;
uploadFile?: { file: File; filename: string };
Expand Down
9 changes: 8 additions & 1 deletion src/utils/resources/secret/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} 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 { k8sCreate, k8sDelete } from '@openshift-console/dynamic-plugin-sdk';

import { getName } from '../shared';

Expand Down Expand Up @@ -94,3 +94,10 @@ export const createSecret: CreateSecretType = ({ namespace, password, secretName
model: SecretModel,
ns: namespace,
});

export const deleteSecret = (secret: IoK8sApiCoreV1Secret) =>
secret &&
k8sDelete({
model: SecretModel,
resource: secret,
});
5 changes: 2 additions & 3 deletions src/utils/resources/template/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +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 { vmBootDiskSourceIsRegistry } from '@kubevirt-utils/resources/vm/utils/source';
import { generatePrettyName } from '@kubevirt-utils/utils/utils';

import { ANNOTATIONS } from './annotations';
Expand Down Expand Up @@ -74,6 +74,5 @@ export const generateParamsWithPrettyName = (template: V1Template) => {

export const bootDiskSourceIsRegistry = (template: V1Template) => {
const vmObject: V1VirtualMachine = getTemplateVirtualMachineObject(template);
const rootDataVolumeTemplateSpec = getRootDataVolumeTemplateSpec(vmObject);
return Boolean(rootDataVolumeTemplateSpec?.spec?.source?.registry);
return vmBootDiskSourceIsRegistry(vmObject);
};
Loading

0 comments on commit e80c61c

Please sign in to comment.